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