1/*
2   Authentication tests
3   Copyright (C) 2001-2009, Joe Orton <joe@manyfish.co.uk>
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19*/
20
21#include "config.h"
22
23#include <sys/types.h>
24
25#ifdef HAVE_STDLIB_H
26#include <stdlib.h>
27#endif
28#ifdef HAVE_UNISTD_H
29#include <unistd.h>
30#endif
31
32#include "ne_request.h"
33#include "ne_auth.h"
34#include "ne_basic.h"
35#include "ne_md5.h"
36
37#include "tests.h"
38#include "child.h"
39#include "utils.h"
40
41static const char username[] = "Aladdin", password[] = "open sesame";
42static int auth_failed;
43
44#define BASIC_WALLY "Basic realm=WallyWorld"
45#define CHAL_WALLY "WWW-Authenticate: " BASIC_WALLY
46
47#define EOL "\r\n"
48
49static int auth_cb(void *userdata, const char *realm, int tries,
50		   char *un, char *pw)
51{
52    if (strcmp(realm, "WallyWorld")) {
53        NE_DEBUG(NE_DBG_HTTP, "Got wrong realm '%s'!\n", realm);
54        return -1;
55    }
56    strcpy(un, username);
57    strcpy(pw, password);
58    return tries;
59}
60
61static void auth_hdr(char *value)
62{
63#define B "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
64    auth_failed = strcmp(value, B);
65    NE_DEBUG(NE_DBG_HTTP, "Got auth header: [%s]\nWanted header:   [%s]\n"
66	     "Result: %d\n", value, B, auth_failed);
67#undef B
68}
69
70/* Sends a response with given response-code. If hdr is not NULL,
71 * sends that header string too (appending an EOL).  If eoc is
72 * non-zero, request must be last sent down a connection; otherwise,
73 * clength 0 is sent to maintain a persistent connection. */
74static int send_response(ne_socket *sock, const char *hdr, int code, int eoc)
75{
76    char buffer[BUFSIZ];
77
78    sprintf(buffer, "HTTP/1.1 %d Blah Blah" EOL, code);
79
80    if (hdr) {
81	strcat(buffer, hdr);
82	strcat(buffer, EOL);
83    }
84
85    if (eoc) {
86	strcat(buffer, "Connection: close" EOL EOL);
87    } else {
88	strcat(buffer, "Content-Length: 0" EOL EOL);
89    }
90
91    return SEND_STRING(sock, buffer);
92}
93
94/* Server function which sends two responses: first requires auth,
95 * second doesn't. */
96static int auth_serve(ne_socket *sock, void *userdata)
97{
98    char *hdr = userdata;
99
100    auth_failed = 1;
101
102    /* Register globals for discard_request. */
103    got_header = auth_hdr;
104    want_header = "Authorization";
105
106    discard_request(sock);
107    send_response(sock, hdr, 401, 0);
108
109    discard_request(sock);
110    send_response(sock, NULL, auth_failed?500:200, 1);
111
112    return 0;
113}
114
115/* Test that various Basic auth challenges are correctly handled. */
116static int basic(void)
117{
118    const char *hdrs[] = {
119        /* simplest case */
120        CHAL_WALLY,
121
122        /* several challenges, one header */
123        "WWW-Authenticate: BarFooScheme, " BASIC_WALLY,
124
125        /* several challenges, one header */
126        CHAL_WALLY ", BarFooScheme realm=\"PenguinWorld\"",
127
128        /* whitespace tests. */
129        "WWW-Authenticate:   Basic realm=WallyWorld   ",
130
131        /* nego test. */
132        "WWW-Authenticate: Negotiate fish, Basic realm=WallyWorld",
133
134        /* nego test. */
135        "WWW-Authenticate: Negotiate fish, bar=boo, Basic realm=WallyWorld",
136
137        /* nego test. */
138        "WWW-Authenticate: Negotiate, Basic realm=WallyWorld",
139
140        /* multi-header case 1 */
141        "WWW-Authenticate: BarFooScheme\r\n"
142        CHAL_WALLY,
143
144        /* multi-header cases 1 */
145        CHAL_WALLY "\r\n"
146        "WWW-Authenticate: BarFooScheme bar=\"foo\"",
147
148        /* multi-header case 3 */
149        "WWW-Authenticate: FooBarChall foo=\"bar\"\r\n"
150        CHAL_WALLY "\r\n"
151        "WWW-Authenticate: BarFooScheme bar=\"foo\"",
152
153        /* quoting test; fails to handle scheme properly with <= 0.28.2. */
154        "WWW-Authenticate: Basic realm=\"WallyWorld\" , BarFooScheme"
155    };
156    size_t n;
157
158    for (n = 0; n < sizeof(hdrs)/sizeof(hdrs[0]); n++) {
159        ne_session *sess;
160
161        CALL(make_session(&sess, auth_serve, (void *)hdrs[n]));
162        ne_set_server_auth(sess, auth_cb, NULL);
163
164        CALL(any_2xx_request(sess, "/norman"));
165
166        ne_session_destroy(sess);
167        CALL(await_server());
168    }
169
170    return OK;
171}
172
173static int retry_serve(ne_socket *sock, void *ud)
174{
175    discard_request(sock);
176    send_response(sock, CHAL_WALLY, 401, 0);
177
178    discard_request(sock);
179    send_response(sock, CHAL_WALLY, 401, 0);
180
181    discard_request(sock);
182    send_response(sock, NULL, 200, 0);
183
184    discard_request(sock);
185    send_response(sock, CHAL_WALLY, 401, 0);
186
187    discard_request(sock);
188    send_response(sock, NULL, 200, 0);
189
190    discard_request(sock);
191    send_response(sock, NULL, 200, 0);
192
193    discard_request(sock);
194    send_response(sock, NULL, 200, 0);
195
196    discard_request(sock);
197    send_response(sock, CHAL_WALLY, 401, 0);
198
199    discard_request(sock);
200    send_response(sock, NULL, 200, 0);
201
202    discard_request(sock);
203    send_response(sock, CHAL_WALLY, 401, 0);
204
205    discard_request(sock);
206    send_response(sock, CHAL_WALLY, 401, 0);
207
208    discard_request(sock);
209    send_response(sock, CHAL_WALLY, 401, 0);
210
211    discard_request(sock);
212    send_response(sock, NULL, 200, 0);
213
214    return OK;
215}
216
217static int retry_cb(void *userdata, const char *realm, int tries,
218		    char *un, char *pw)
219{
220    int *count = userdata;
221
222    /* dummy creds; server ignores them anyway. */
223    strcpy(un, "a");
224    strcpy(pw, "b");
225
226    switch (*count) {
227    case 0:
228    case 1:
229	if (tries == *count) {
230	    *count += 1;
231	    return 0;
232	} else {
233	    t_context("On request #%d, got attempt #%d", *count, tries);
234	    *count = -1;
235	    return 1;
236	}
237	break;
238    case 2:
239    case 3:
240	/* server fails a subsequent request, check that tries has
241	 * reset to zero. */
242	if (tries == 0) {
243	    *count += 1;
244	    return 0;
245	} else {
246	    t_context("On retry after failure #%d, tries was %d",
247		      *count, tries);
248	    *count = -1;
249	    return 1;
250	}
251	break;
252    case 4:
253    case 5:
254	if (tries > 1) {
255	    t_context("Attempt counter reached #%d", tries);
256	    *count = -1;
257	    return 1;
258	}
259	return tries;
260    default:
261	t_context("Count reached %d!?", *count);
262	*count = -1;
263    }
264    return 1;
265}
266
267/* Test that auth retries are working correctly. */
268static int retries(void)
269{
270    ne_session *sess;
271    int count = 0;
272
273    CALL(make_session(&sess, retry_serve, NULL));
274
275    ne_set_server_auth(sess, retry_cb, &count);
276
277    /* This request will be 401'ed twice, then succeed. */
278    ONREQ(any_request(sess, "/foo"));
279
280    /* auth_cb will have set up context. */
281    CALL(count != 2);
282
283    /* this request will be 401'ed once, then succeed. */
284    ONREQ(any_request(sess, "/foo"));
285
286    /* auth_cb will have set up context. */
287    CALL(count != 3);
288
289    /* some 20x requests. */
290    ONREQ(any_request(sess, "/foo"));
291    ONREQ(any_request(sess, "/foo"));
292
293    /* this request will be 401'ed once, then succeed. */
294    ONREQ(any_request(sess, "/foo"));
295
296    /* auth_cb will have set up context. */
297    CALL(count != 4);
298
299    /* First request is 401'ed by the server at both attempts. */
300    ONV(any_request(sess, "/foo") != NE_AUTH,
301	("auth succeeded, should have failed: %s", ne_get_error(sess)));
302
303    count++;
304
305    /* Second request is 401'ed first time, then will succeed if
306     * retried.  0.18.0 didn't reset the attempt counter though so
307     * this didn't work. */
308    ONV(any_request(sess, "/foo") == NE_AUTH,
309	("auth failed on second try, should have succeeded: %s", ne_get_error(sess)));
310
311    ne_session_destroy(sess);
312
313    CALL(await_server());
314
315    return OK;
316}
317
318/* crashes with neon <0.22 */
319static int forget_regress(void)
320{
321    ne_session *sess = ne_session_create("http", "localhost", 7777);
322    ne_forget_auth(sess);
323    ne_session_destroy(sess);
324    return OK;
325}
326
327static int fail_auth_cb(void *ud, const char *realm, int attempt,
328			char *un, char *pw)
329{
330    return 1;
331}
332
333/* this may trigger a segfault in neon 0.21.x and earlier. */
334static int tunnel_regress(void)
335{
336    ne_session *sess = ne_session_create("https", "localhost", 443);
337    ne_session_proxy(sess, "localhost", 7777);
338    ne_set_server_auth(sess, fail_auth_cb, NULL);
339    CALL(spawn_server(7777, single_serve_string,
340		      "HTTP/1.1 401 Auth failed.\r\n"
341		      "WWW-Authenticate: Basic realm=asda\r\n"
342		      "Content-Length: 0\r\n\r\n"));
343    any_request(sess, "/foo");
344    ne_session_destroy(sess);
345    CALL(await_server());
346    return OK;
347}
348
349/* regression test for parsing a Negotiate challenge with on parameter
350 * token. */
351static int negotiate_regress(void)
352{
353    ne_session *sess = ne_session_create("http", "localhost", 7777);
354    ne_set_server_auth(sess, fail_auth_cb, NULL);
355    CALL(spawn_server(7777, single_serve_string,
356		      "HTTP/1.1 401 Auth failed.\r\n"
357		      "WWW-Authenticate: Negotiate\r\n"
358		      "Content-Length: 0\r\n\r\n"));
359    any_request(sess, "/foo");
360    ne_session_destroy(sess);
361    CALL(await_server());
362    return OK;
363}
364
365static char *digest_hdr = NULL;
366
367static void dup_header(char *header)
368{
369    if (digest_hdr) ne_free(digest_hdr);
370    digest_hdr = ne_strdup(header);
371}
372
373struct digest_parms {
374    const char *realm, *nonce, *opaque, *domain;
375    int rfc2617;
376    int send_ainfo;
377    int md5_sess;
378    int proxy;
379    int send_nextnonce;
380    int num_requests;
381    int stale;
382    enum digest_failure {
383        fail_not,
384        fail_bogus_alg,
385        fail_req0_stale,
386        fail_req0_2069_stale,
387        fail_omit_qop,
388        fail_omit_realm,
389        fail_omit_nonce,
390        fail_ai_bad_nc,
391        fail_ai_bad_nc_syntax,
392        fail_ai_bad_digest,
393        fail_ai_bad_cnonce,
394        fail_ai_omit_cnonce,
395        fail_ai_omit_digest,
396        fail_ai_omit_nc,
397        fail_outside_domain
398    } failure;
399};
400
401struct digest_state {
402    const char *realm, *nonce, *uri, *username, *password, *algorithm, *qop,
403        *method, *opaque;
404    char *cnonce, *digest, *ncval;
405    long nc;
406};
407
408/* Write the request-digest into 'digest' (or response-digest if
409 * auth_info is non-zero) for given digest auth state and
410 * parameters.  */
411static void make_digest(struct digest_state *state, struct digest_parms *parms,
412                        int auth_info, char digest[33])
413{
414    struct ne_md5_ctx *ctx;
415    char h_a1[33], h_a2[33];
416
417    /* H(A1) */
418    ctx = ne_md5_create_ctx();
419    ne_md5_process_bytes(state->username, strlen(state->username), ctx);
420    ne_md5_process_bytes(":", 1, ctx);
421    ne_md5_process_bytes(state->realm, strlen(state->realm), ctx);
422    ne_md5_process_bytes(":", 1, ctx);
423    ne_md5_process_bytes(state->password, strlen(state->password), ctx);
424    ne_md5_finish_ascii(ctx, h_a1);
425
426    if (parms->md5_sess) {
427        ne_md5_reset_ctx(ctx);
428        ne_md5_process_bytes(h_a1, 32, ctx);
429        ne_md5_process_bytes(":", 1, ctx);
430        ne_md5_process_bytes(state->nonce, strlen(state->nonce), ctx);
431        ne_md5_process_bytes(":", 1, ctx);
432        ne_md5_process_bytes(state->cnonce, strlen(state->cnonce), ctx);
433        ne_md5_finish_ascii(ctx, h_a1);
434    }
435
436    /* H(A2) */
437    ne_md5_reset_ctx(ctx);
438    if (!auth_info)
439        ne_md5_process_bytes(state->method, strlen(state->method), ctx);
440    ne_md5_process_bytes(":", 1, ctx);
441    ne_md5_process_bytes(state->uri, strlen(state->uri), ctx);
442    ne_md5_finish_ascii(ctx, h_a2);
443
444    /* request-digest */
445    ne_md5_reset_ctx(ctx);
446    ne_md5_process_bytes(h_a1, strlen(h_a1), ctx);
447    ne_md5_process_bytes(":", 1, ctx);
448    ne_md5_process_bytes(state->nonce, strlen(state->nonce), ctx);
449    ne_md5_process_bytes(":", 1, ctx);
450
451    if (parms->rfc2617) {
452        ne_md5_process_bytes(state->ncval, strlen(state->ncval), ctx);
453        ne_md5_process_bytes(":", 1, ctx);
454        ne_md5_process_bytes(state->cnonce, strlen(state->cnonce), ctx);
455        ne_md5_process_bytes(":", 1, ctx);
456        ne_md5_process_bytes(state->qop, strlen(state->qop), ctx);
457        ne_md5_process_bytes(":", 1, ctx);
458    }
459
460    ne_md5_process_bytes(h_a2, strlen(h_a2), ctx);
461    ne_md5_finish_ascii(ctx, digest);
462    ne_md5_destroy_ctx(ctx);
463}
464
465/* Verify that the response-digest matches expected state. */
466static int check_digest(struct digest_state *state, struct digest_parms *parms)
467{
468    char digest[33];
469
470    make_digest(state, parms, 0, digest);
471
472    ONV(strcmp(digest, state->digest),
473        ("bad digest; expected %s got %s", state->digest, digest));
474
475    return OK;
476}
477
478#define DIGCMP(field)                                   \
479    do {                                                \
480        ONCMP(state->field, newstate.field,            \
481              "Digest response header", #field);        \
482    } while (0)
483
484#define PARAM(field)                                            \
485    do {                                                        \
486        if (ne_strcasecmp(name, #field) == 0) {                 \
487            ONV(newstate.field != NULL,                        \
488                ("received multiple %s params: %s, %s", #field, \
489                 newstate.field, val));                        \
490            newstate.field = val;                              \
491        }                                                       \
492    } while (0)
493
494/* Verify that Digest auth request header, 'header', meets expected
495 * state and parameters. */
496static int verify_digest_header(struct digest_state *state,
497                                struct digest_parms *parms,
498                                char *header)
499{
500    char *ptr;
501    struct digest_state newstate = {0};
502
503    ptr = ne_token(&header, ' ');
504
505    ONCMP("Digest", ptr, "Digest response", "scheme name");
506
507    while (header) {
508        char *name, *val;
509
510        ptr = ne_qtoken(&header, ',', "\"\'");
511        ONN("quoting broken", ptr == NULL);
512
513        name = ne_shave(ptr, " ");
514
515        val = strchr(name, '=');
516        ONV(val == NULL, ("bad name/value pair: %s", val));
517
518        *val++ = '\0';
519
520        val = ne_shave(val, "\"\' ");
521
522        NE_DEBUG(NE_DBG_HTTP, "got field: [%s] = [%s]\n", name, val);
523
524        PARAM(uri);
525        PARAM(realm);
526        PARAM(username);
527        PARAM(nonce);
528        PARAM(algorithm);
529        PARAM(qop);
530        PARAM(opaque);
531        PARAM(cnonce);
532
533        if (ne_strcasecmp(name, "nc") == 0) {
534            long nc = strtol(val, NULL, 16);
535
536            ONV(nc != state->nc,
537                ("got bad nonce count: %ld (%s) not %ld",
538                 nc, val, state->nc));
539
540            state->ncval = ne_strdup(val);
541        }
542        else if (ne_strcasecmp(name, "response") == 0) {
543            state->digest = ne_strdup(val);
544        }
545    }
546
547    ONN("cnonce param missing for 2617-style auth",
548        parms->rfc2617 && !newstate.cnonce);
549
550    DIGCMP(realm);
551    DIGCMP(username);
552    if (!parms->domain)
553        DIGCMP(uri);
554    DIGCMP(nonce);
555    DIGCMP(opaque);
556    DIGCMP(algorithm);
557
558    if (parms->rfc2617) {
559        DIGCMP(qop);
560    }
561
562    if (newstate.cnonce) {
563        state->cnonce = ne_strdup(newstate.cnonce);
564    }
565    if (parms->domain) {
566        state->uri = ne_strdup(newstate.uri);
567    }
568
569    ONN("no digest param given", !state->digest);
570
571    CALL(check_digest(state, parms));
572
573    state->nc++;
574
575    return OK;
576}
577
578static char *make_authinfo_header(struct digest_state *state,
579                                  struct digest_parms *parms)
580{
581    ne_buffer *buf = ne_buffer_create();
582    char digest[33], *ncval, *cnonce;
583
584    if (parms->failure == fail_ai_bad_digest) {
585        strcpy(digest, "fish");
586    } else {
587        make_digest(state, parms, 1, digest);
588    }
589
590    if (parms->failure == fail_ai_bad_nc_syntax) {
591        ncval = "zztop";
592    } else if (parms->failure == fail_ai_bad_nc) {
593        ncval = "999";
594    } else {
595        ncval = state->ncval;
596    }
597
598    if (parms->failure == fail_ai_bad_cnonce) {
599        cnonce = "another-fish";
600    } else {
601        cnonce = state->cnonce;
602    }
603
604    if (parms->proxy) {
605        ne_buffer_czappend(buf, "Proxy-");
606    }
607
608    ne_buffer_czappend(buf, "Authentication-Info: ");
609
610    if (!parms->rfc2617) {
611        ne_buffer_concat(buf, "rspauth=\"", digest, "\"", NULL);
612    } else {
613        if (parms->failure != fail_ai_omit_nc) {
614            ne_buffer_concat(buf, "nc=", ncval, ", ", NULL);
615        }
616        if (parms->failure != fail_ai_omit_cnonce) {
617            ne_buffer_concat(buf, "cnonce=\"", cnonce, "\", ", NULL);
618        }
619        if (parms->failure != fail_ai_omit_digest) {
620            ne_buffer_concat(buf, "rspauth=\"", digest, "\", ", NULL);
621        }
622        if (parms->send_nextnonce) {
623            state->nonce = ne_concat("next-", state->nonce, NULL);
624            ne_buffer_concat(buf, "nextnonce=\"", state->nonce, "\", ", NULL);
625            state->nc = 1;
626        }
627        ne_buffer_czappend(buf, "qop=\"auth\"");
628    }
629
630    return ne_buffer_finish(buf);
631}
632
633static char *make_digest_header(struct digest_state *state,
634                                struct digest_parms *parms)
635{
636    ne_buffer *buf = ne_buffer_create();
637    const char *algorithm;
638
639    algorithm = parms->failure == fail_bogus_alg ? "fish"
640        : state->algorithm;
641
642    ne_buffer_concat(buf,
643                     parms->proxy ? "Proxy-Authenticate"
644                     : "WWW-Authenticate",
645                     ": Digest "
646                     "realm=\"", parms->realm, "\", ", NULL);
647
648    if (parms->rfc2617) {
649        ne_buffer_concat(buf, "algorithm=\"", algorithm, "\", ",
650                         "qop=\"", state->qop, "\", ", NULL);
651    }
652
653    if (parms->opaque) {
654        ne_buffer_concat(buf, "opaque=\"", parms->opaque, "\", ", NULL);
655    }
656
657    if (parms->domain) {
658        ne_buffer_concat(buf, "domain=\"", parms->domain, "\", ", NULL);
659    }
660
661    if (parms->failure == fail_req0_stale
662        || parms->failure == fail_req0_2069_stale
663        || parms->stale == parms->num_requests) {
664        ne_buffer_concat(buf, "stale='true', ", NULL);
665    }
666
667    ne_buffer_concat(buf, "nonce=\"", state->nonce, "\"", NULL);
668
669    return ne_buffer_finish(buf);
670}
671
672/* Server process for Digest auth handling. */
673static int serve_digest(ne_socket *sock, void *userdata)
674{
675    struct digest_parms *parms = userdata;
676    struct digest_state state;
677    char resp[NE_BUFSIZ];
678
679    if (parms->proxy)
680        state.uri = "http://www.example.com/fish";
681    else if (parms->domain)
682        state.uri = "/fish/0";
683    else
684        state.uri = "/fish";
685    state.method = "GET";
686    state.realm = parms->realm;
687    state.nonce = parms->nonce;
688    state.opaque = parms->opaque;
689    state.username = username;
690    state.password = password;
691    state.nc = 1;
692    state.algorithm = parms->md5_sess ? "MD5-sess" : "MD5";
693    state.qop = "auth";
694
695    state.cnonce = state.digest = state.ncval = NULL;
696
697    parms->num_requests += parms->stale ? 1 : 0;
698
699    NE_DEBUG(NE_DBG_HTTP, ">>>> Response sequence begins, %d requests.\n",
700             parms->num_requests);
701
702    want_header = parms->proxy ? "Proxy-Authorization" : "Authorization";
703    digest_hdr = NULL;
704    got_header = dup_header;
705
706    CALL(discard_request(sock));
707
708    ONV(digest_hdr != NULL,
709        ("got unwarranted WWW-Auth header: %s", digest_hdr));
710
711    ne_snprintf(resp, sizeof resp,
712                "HTTP/1.1 %d Auth Denied\r\n"
713                "%s\r\n"
714                "Content-Length: 0\r\n" "\r\n",
715                parms->proxy ? 407 : 401,
716                make_digest_header(&state, parms));
717
718    SEND_STRING(sock, resp);
719
720    /* Give up now if we've sent a challenge which should force the
721     * client to fail immediately: */
722    if (parms->failure == fail_bogus_alg
723        || parms->failure == fail_req0_stale
724        || parms->failure == fail_req0_2069_stale) {
725        return OK;
726    }
727
728    do {
729        digest_hdr = NULL;
730        CALL(discard_request(sock));
731
732        if (digest_hdr && parms->domain && (parms->num_requests & 1) != 0) {
733            SEND_STRING(sock, "HTTP/1.1 400 Used Auth Outside Domain\r\n\r\n");
734            return OK;
735        }
736        else if (digest_hdr == NULL && parms->domain && (parms->num_requests & 1) != 0) {
737            /* Do nothing. */
738            NE_DEBUG(NE_DBG_HTTP, "No Authorization header sent, good.\n");
739        }
740        else {
741            ONN("no Authorization header sent", digest_hdr == NULL);
742
743            CALL(verify_digest_header(&state, parms, digest_hdr));
744        }
745
746        if (parms->num_requests == parms->stale) {
747            state.nonce = ne_concat("stale-", state.nonce, NULL);
748            state.nc = 1;
749
750            ne_snprintf(resp, sizeof resp,
751                        "HTTP/1.1 %d Auth Denied\r\n"
752                        "%s\r\n"
753                        "Content-Length: 0\r\n" "\r\n",
754                        parms->proxy ? 407 : 401,
755                        make_digest_header(&state, parms));
756        }
757        else if (parms->send_ainfo) {
758            char *ai = make_authinfo_header(&state, parms);
759
760            ne_snprintf(resp, sizeof resp,
761                        "HTTP/1.1 200 Well, if you insist\r\n"
762                        "Content-Length: 0\r\n"
763                        "%s\r\n"
764                        "\r\n", ai);
765
766            ne_free(ai);
767        } else {
768            ne_snprintf(resp, sizeof resp,
769                        "HTTP/1.1 200 You did good\r\n"
770                        "Content-Length: 0\r\n" "\r\n");
771        }
772
773        SEND_STRING(sock, resp);
774
775        NE_DEBUG(NE_DBG_HTTP, "Handled request; %d requests remain.\n",
776                 parms->num_requests - 1);
777    } while (--parms->num_requests);
778
779    return OK;
780}
781
782static int test_digest(struct digest_parms *parms)
783{
784    ne_session *sess;
785
786    NE_DEBUG(NE_DBG_HTTP, ">>>> Request sequence begins "
787             "(nonce=%s, rfc=%s, stale=%d).\n",
788             parms->nonce, parms->rfc2617 ? "2617" : "2069",
789             parms->stale);
790
791    if (parms->proxy) {
792        sess = ne_session_create("http", "www.example.com", 80);
793        ne_session_proxy(sess, "localhost", 7777);
794        ne_set_proxy_auth(sess, auth_cb, NULL);
795    }
796    else {
797        sess = ne_session_create("http", "localhost", 7777);
798        ne_set_server_auth(sess, auth_cb, NULL);
799    }
800
801    CALL(spawn_server(7777, serve_digest, parms));
802
803    do {
804        CALL(any_2xx_request(sess, "/fish"));
805    } while (--parms->num_requests);
806
807    ne_session_destroy(sess);
808    return await_server();
809}
810
811/* Test for RFC2617-style Digest auth. */
812static int digest(void)
813{
814    struct digest_parms parms[] = {
815        /* RFC 2617-style */
816        { "WallyWorld", "this-is-a-nonce", NULL, NULL, 1, 0, 0, 0, 0, 1, 0, fail_not },
817        { "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, 1, 0, 0, 0, 0, 1, 0, fail_not },
818        /* ... with A-I */
819        { "WallyWorld", "nonce-nonce-nonce", "opaque-string", NULL, 1, 1, 0, 0, 0, 1, 0, fail_not },
820        /* ... with md5-sess. */
821        { "WallyWorld", "nonce-nonce-nonce", "opaque-string", NULL, 1, 1, 1, 0, 0, 1, 0, fail_not },
822        /* many requests, with changing nonces; tests for next-nonce handling bug. */
823        { "WallyWorld", "this-is-a-nonce", "opaque-thingy", NULL, 1, 1, 0, 0, 1, 20, 0, fail_not },
824
825        /* staleness. */
826        { "WallyWorld", "this-is-a-nonce", "opaque-thingy", NULL, 1, 1, 0, 0, 0, 3, 2, fail_not },
827        /* 2069 + stale */
828        { "WallyWorld", "this-is-a-nonce", NULL, NULL, 0, 1, 0, 0, 0, 3, 2, fail_not },
829
830        /* RFC 2069-style */
831        { "WallyWorld", "lah-di-da-di-dah", NULL, NULL, 0, 0, 0, 0, 0, 1, 0, fail_not },
832        { "WallyWorld", "fee-fi-fo-fum", "opaque-string", NULL, 0, 0, 0, 0, 0, 1, 0, fail_not },
833        { "WallyWorld", "fee-fi-fo-fum", "opaque-string", NULL, 0, 1, 0, 0, 0, 1, 0, fail_not },
834
835        /* Proxy auth */
836        { "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, 1, 1, 0, 0, 0, 1, 0, fail_not },
837        /* Proxy + A-I */
838        { "WallyWorld", "this-is-also-a-nonce", "opaque-string", NULL, 1, 1, 0, 1, 0, 1, 0, fail_not },
839
840        { NULL }
841    };
842    size_t n;
843
844    for (n = 0; parms[n].realm; n++) {
845        CALL(test_digest(&parms[n]));
846
847    }
848
849    return OK;
850}
851
852static int digest_failures(void)
853{
854    struct digest_parms parms;
855    static const struct {
856        enum digest_failure mode;
857        const char *message;
858    } fails[] = {
859        { fail_ai_bad_nc, "nonce count mismatch" },
860        { fail_ai_bad_nc_syntax, "could not parse nonce count" },
861        { fail_ai_bad_digest, "digest mismatch" },
862        { fail_ai_bad_cnonce, "client nonce mismatch" },
863        { fail_ai_omit_nc, "missing parameters" },
864        { fail_ai_omit_digest, "missing parameters" },
865        { fail_ai_omit_cnonce, "missing parameters" },
866        { fail_bogus_alg, "unknown algorithm" },
867        { fail_req0_stale, "initial Digest challenge was stale" },
868        { fail_req0_2069_stale, "initial Digest challenge was stale" },
869        { fail_not, NULL }
870    };
871    size_t n;
872
873    memset(&parms, 0, sizeof parms);
874
875    parms.realm = "WallyWorld";
876    parms.nonce = "random-invented-string";
877    parms.opaque = NULL;
878    parms.send_ainfo = 1;
879    parms.num_requests = 1;
880
881    for (n = 0; fails[n].message; n++) {
882        ne_session *sess = ne_session_create("http", "localhost", 7777);
883        int ret;
884
885        parms.failure = fails[n].mode;
886
887        if (parms.failure == fail_req0_2069_stale)
888            parms.rfc2617 = 0;
889        else
890            parms.rfc2617 = 1;
891
892        NE_DEBUG(NE_DBG_HTTP, ">>> New Digest failure test, "
893                 "expecting failure '%s'\n", fails[n].message);
894
895        ne_set_server_auth(sess, auth_cb, NULL);
896        CALL(spawn_server(7777, serve_digest, &parms));
897
898        ret = any_2xx_request(sess, "/fish");
899        ONV(ret == NE_OK,
900            ("request success; expecting error '%s'",
901             fails[n].message));
902
903        ONV(strstr(ne_get_error(sess), fails[n].message) == NULL,
904            ("request fails with error '%s'; expecting '%s'",
905             ne_get_error(sess), fails[n].message));
906
907        ne_session_destroy(sess);
908
909        if (fails[n].mode == fail_bogus_alg
910            || fails[n].mode == fail_req0_stale) {
911            reap_server();
912        } else {
913            CALL(await_server());
914        }
915    }
916
917    return OK;
918}
919
920static int fail_cb(void *userdata, const char *realm, int tries,
921		   char *un, char *pw)
922{
923    ne_buffer *buf = userdata;
924    char str[64];
925
926    ne_snprintf(str, sizeof str, "<%s, %d>", realm, tries);
927    ne_buffer_zappend(buf, str);
928
929    return -1;
930}
931
932static int fail_challenge(void)
933{
934    static const struct {
935        const char *resp, *error, *challs;
936    } ts[] = {
937        /* only possible Basic parse failure. */
938        { "Basic", "missing realm in Basic challenge" },
939
940        /* Digest parameter invalid/omitted failure cases: */
941        { "Digest algorithm=MD5, qop=auth, nonce=\"foo\"",
942          "missing parameter in Digest challenge" },
943        { "Digest algorithm=MD5, qop=auth, realm=\"foo\"",
944          "missing parameter in Digest challenge" },
945        { "Digest algorithm=ZEBEDEE-GOES-BOING, qop=auth, realm=\"foo\"",
946          "unknown algorithm in Digest challenge" },
947        { "Digest algorithm=MD5-sess, realm=\"foo\"",
948          "incompatible algorithm in Digest challenge" },
949        { "Digest algorithm=MD5, qop=auth, nonce=\"foo\", realm=\"foo\", "
950          "domain=\"http://[::1/\"", "could not parse domain" },
951
952        /* Multiple challenge failure cases: */
953        { "Basic, Digest",
954          "missing parameter in Digest challenge, missing realm in Basic challenge" },
955
956        { "Digest realm=\"foo\", algorithm=MD5, qop=auth, nonce=\"foo\","
957          " Basic realm=\"foo\"",
958          "rejected Digest challenge, rejected Basic challenge" },
959
960        { "WhizzBangAuth realm=\"foo\", "
961          "Basic realm='foo'",
962          "ignored WhizzBangAuth challenge, rejected Basic challenge" },
963        { "", "could not parse challenge" },
964
965        /* neon 0.26.x regression in "attempt" handling. */
966        { "Basic realm=\"foo\", "
967          "Digest realm=\"bar\", algorithm=MD5, qop=auth, nonce=\"foo\"",
968          "rejected Digest challenge, rejected Basic challenge"
969          , "<bar, 0><foo, 1>"  /* Digest challenge first, Basic second. */
970        }
971    };
972    unsigned n;
973
974    for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
975        char resp[512];
976        ne_session *sess;
977        int ret;
978        ne_buffer *buf = ne_buffer_create();
979
980        ne_snprintf(resp, sizeof resp,
981                    "HTTP/1.1 401 Auth Denied\r\n"
982                    "WWW-Authenticate: %s\r\n"
983                    "Content-Length: 0\r\n" "\r\n",
984                    ts[n].resp);
985
986        CALL(make_session(&sess, single_serve_string, resp));
987
988        ne_set_server_auth(sess, fail_cb, buf);
989
990        ret = any_2xx_request(sess, "/fish");
991        ONV(ret == NE_OK,
992            ("request success; expecting error '%s'",
993             ts[n].error));
994
995        ONV(strstr(ne_get_error(sess), ts[n].error) == NULL,
996            ("request fails with error '%s'; expecting '%s'",
997             ne_get_error(sess), ts[n].error));
998
999        if (ts[n].challs) {
1000            ONCMP(ts[n].challs, buf->data, "challenge callback",
1001                  "invocation order");
1002        }
1003
1004        ne_session_destroy(sess);
1005        ne_buffer_destroy(buf);
1006        CALL(await_server());
1007    }
1008
1009    return OK;
1010}
1011
1012struct multi_context {
1013    int id;
1014    ne_buffer *buf;
1015};
1016
1017static int multi_cb(void *userdata, const char *realm, int tries,
1018                    char *un, char *pw)
1019{
1020    struct multi_context *ctx = userdata;
1021
1022    ne_buffer_snprintf(ctx->buf, 128, "[id=%d, realm=%s, tries=%d]",
1023                       ctx->id, realm, tries);
1024
1025    return -1;
1026}
1027
1028static int multi_handler(void)
1029{
1030    ne_session *sess;
1031    struct multi_context c[2];
1032    unsigned n;
1033    ne_buffer *buf = ne_buffer_create();
1034
1035    CALL(make_session(&sess, single_serve_string,
1036                      "HTTP/1.1 401 Auth Denied\r\n"
1037                      "WWW-Authenticate: Basic realm='fish',"
1038                      " Digest realm='food', algorithm=MD5, qop=auth, nonce=gaga\r\n"
1039                      "Content-Length: 0\r\n" "\r\n"));
1040
1041    for (n = 0; n < 2; n++) {
1042        c[n].buf = buf;
1043        c[n].id = n + 1;
1044    }
1045
1046    ne_add_server_auth(sess, NE_AUTH_BASIC, multi_cb, &c[0]);
1047    ne_add_server_auth(sess, NE_AUTH_DIGEST, multi_cb, &c[1]);
1048
1049    any_request(sess, "/fish");
1050
1051    ONCMP("[id=2, realm=food, tries=0]"
1052          "[id=1, realm=fish, tries=0]", buf->data,
1053          "multiple callback", "invocation order");
1054
1055    ne_session_destroy(sess);
1056    ne_buffer_destroy(buf);
1057
1058    return await_server();
1059}
1060
1061static int domains(void)
1062{
1063    ne_session *sess;
1064    struct digest_parms parms;
1065
1066    memset(&parms, 0, sizeof parms);
1067    parms.realm = "WallyWorld";
1068    parms.rfc2617 = 1;
1069    parms.nonce = "agoog";
1070    parms.domain = "http://localhost:7777/fish/ https://example.com /agaor /other";
1071    parms.num_requests = 6;
1072
1073    CALL(make_session(&sess, serve_digest, &parms));
1074
1075    ne_set_server_auth(sess, auth_cb, NULL);
1076
1077    ne_session_proxy(sess, "localhost", 7777);
1078
1079    CALL(any_2xx_request(sess, "/fish/0"));
1080    CALL(any_2xx_request(sess, "/outside"));
1081    CALL(any_2xx_request(sess, "/others"));
1082    CALL(any_2xx_request(sess, "/fish"));
1083    CALL(any_2xx_request(sess, "/fish/2"));
1084    CALL(any_2xx_request(sess, "*"));
1085
1086    ne_session_destroy(sess);
1087
1088    return await_server();
1089}
1090
1091/* This segfaulted with 0.28.0 through 0.28.2 inclusive. */
1092static int CVE_2008_3746(void)
1093{
1094    ne_session *sess;
1095    struct digest_parms parms;
1096
1097    memset(&parms, 0, sizeof parms);
1098    parms.realm = "WallyWorld";
1099    parms.rfc2617 = 1;
1100    parms.nonce = "agoog";
1101    parms.domain = "foo";
1102    parms.num_requests = 1;
1103
1104    CALL(make_session(&sess, serve_digest, &parms));
1105
1106    ne_set_server_auth(sess, auth_cb, NULL);
1107
1108    ne_session_proxy(sess, "localhost", 7777);
1109
1110    any_2xx_request(sess, "/fish/0");
1111
1112    ne_session_destroy(sess);
1113
1114    return await_server();
1115}
1116
1117static int defaults(void)
1118{
1119    ne_session *sess;
1120
1121    CALL(make_session(&sess, auth_serve, CHAL_WALLY));
1122    ne_add_server_auth(sess, NE_AUTH_DEFAULT, auth_cb, NULL);
1123    CALL(any_2xx_request(sess, "/norman"));
1124    ne_session_destroy(sess);
1125    CALL(await_server());
1126
1127    CALL(make_session(&sess, auth_serve, CHAL_WALLY));
1128    ne_add_server_auth(sess, NE_AUTH_ALL, auth_cb, NULL);
1129    CALL(any_2xx_request(sess, "/norman"));
1130    ne_session_destroy(sess);
1131    return await_server();
1132}
1133
1134static void fail_hdr(char *value)
1135{
1136    auth_failed = 1;
1137}
1138
1139static int serve_forgotten(ne_socket *sock, void *userdata)
1140{
1141    auth_failed = 0;
1142    got_header = fail_hdr;
1143    want_header = "Authorization";
1144
1145    CALL(discard_request(sock));
1146    if (auth_failed) {
1147        /* Should not get initial Auth header.  Eek. */
1148        send_response(sock, NULL, 403, 1);
1149        return 0;
1150    }
1151    send_response(sock, CHAL_WALLY, 401, 0);
1152
1153    got_header = auth_hdr;
1154    CALL(discard_request(sock));
1155    if (auth_failed) {
1156        send_response(sock, NULL, 403, 1);
1157        return 0;
1158    }
1159    send_response(sock, NULL, 200, 0);
1160
1161    ne_sock_read_timeout(sock, 5);
1162
1163    /* Last time; should get no Auth header. */
1164    got_header = fail_hdr;
1165    CALL(discard_request(sock));
1166    send_response(sock, NULL, auth_failed ? 500 : 200, 1);
1167
1168    return 0;
1169}
1170
1171static int forget(void)
1172{
1173    ne_session *sess;
1174
1175    CALL(make_session(&sess, serve_forgotten, NULL));
1176
1177    ne_set_server_auth(sess, auth_cb, NULL);
1178
1179    CALL(any_2xx_request(sess, "/norman"));
1180
1181    ne_forget_auth(sess);
1182
1183    CALL(any_2xx_request(sess, "/norman"));
1184
1185    ne_session_destroy(sess);
1186
1187    return await_server();
1188}
1189
1190/* proxy auth, proxy AND origin */
1191
1192ne_test tests[] = {
1193    T(lookup_localhost),
1194    T(basic),
1195    T(retries),
1196    T(forget_regress),
1197    T(tunnel_regress),
1198    T(negotiate_regress),
1199    T(digest),
1200    T(digest_failures),
1201    T(fail_challenge),
1202    T(multi_handler),
1203    T(domains),
1204    T(defaults),
1205    T(CVE_2008_3746),
1206    T(forget),
1207    T(NULL)
1208};
1209