1/* 2 HTTP Authentication routines 3 Copyright (C) 1999-2009, Joe Orton <joe@manyfish.co.uk> 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Library General Public 7 License as published by the Free Software Foundation; either 8 version 2 of the License, or (at your option) any later version. 9 10 This library 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 GNU 13 Library General Public License for more details. 14 15 You should have received a copy of the GNU Library General Public 16 License along with this library; if not, write to the Free 17 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 18 MA 02111-1307, USA 19 20*/ 21 22#include "config.h" 23 24#include <sys/types.h> 25 26#ifdef HAVE_SYS_TIME_H 27#include <sys/time.h> 28#endif 29#ifdef HAVE_STDLIB_H 30#include <stdlib.h> 31#endif 32#ifdef HAVE_STRING_H 33#include <string.h> 34#endif 35#ifdef HAVE_STRINGS_H 36#include <strings.h> 37#endif 38#ifdef HAVE_UNISTD_H 39#include <unistd.h> /* for getpid() */ 40#endif 41 42#ifdef WIN32 43#include <windows.h> /* for GetCurrentThreadId() etc */ 44#endif 45 46#ifdef HAVE_OPENSSL 47#include <openssl/rand.h> 48#elif defined(HAVE_GNUTLS) 49#include <gnutls/gnutls.h> 50#if LIBGNUTLS_VERSION_NUMBER < 0x020b00 51#include <gcrypt.h> 52#else 53#include <gnutls/crypto.h> 54#endif 55#endif 56 57#include <errno.h> 58#include <time.h> 59 60#include "ne_md5.h" 61#include "ne_dates.h" 62#include "ne_request.h" 63#include "ne_auth.h" 64#include "ne_string.h" 65#include "ne_utils.h" 66#include "ne_alloc.h" 67#include "ne_uri.h" 68#include "ne_internal.h" 69 70#ifdef HAVE_GSSAPI 71#ifdef HAVE_GSSAPI_GSSAPI_H 72#include <gssapi/gssapi.h> 73#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H 74#include <gssapi/gssapi_generic.h> 75#endif 76#else 77#include <gssapi.h> 78#endif 79#endif 80 81#ifdef HAVE_SSPI 82#include "ne_sspi.h" 83#endif 84 85#ifdef HAVE_NTLM 86#include "ne_ntlm.h" 87#endif 88 89#define HOOK_SERVER_ID "http://webdav.org/neon/hooks/server-auth" 90#define HOOK_PROXY_ID "http://webdav.org/neon/hooks/proxy-auth" 91 92typedef enum { 93 auth_alg_md5, 94 auth_alg_md5_sess, 95 auth_alg_unknown 96} auth_algorithm; 97 98/* Selected method of qop which the client is using */ 99typedef enum { 100 auth_qop_none, 101 auth_qop_auth 102} auth_qop; 103 104/* A callback/userdata pair registered by the application for 105 * a particular set of protocols. */ 106struct auth_handler { 107 unsigned protomask; 108 109 ne_auth_creds creds; 110 void *userdata; 111 int attempt; /* number of invocations of this callback for 112 * current request. */ 113 114 struct auth_handler *next; 115}; 116 117/* A challenge */ 118struct auth_challenge { 119 const struct auth_protocol *protocol; 120 struct auth_handler *handler; 121 const char *realm, *nonce, *opaque, *domain; 122 unsigned int stale; /* if stale=true */ 123 unsigned int got_qop; /* we were given a qop directive */ 124 unsigned int qop_auth; /* "auth" token in qop attrib */ 125 auth_algorithm alg; 126 struct auth_challenge *next; 127}; 128 129static const struct auth_class { 130 const char *id, *req_hdr, *resp_hdr, *resp_info_hdr; 131 int status_code; /* Response status-code to trap. */ 132 int fail_code; /* NE_* request to fail with. */ 133 const char *error_noauth; /* Error message template use when 134 * giving up authentication attempts. */ 135} ah_server_class = { 136 HOOK_SERVER_ID, 137 "Authorization", "WWW-Authenticate", "Authentication-Info", 138 401, NE_AUTH, 139 N_("Could not authenticate to server: %s") 140}, ah_proxy_class = { 141 HOOK_PROXY_ID, 142 "Proxy-Authorization", "Proxy-Authenticate", "Proxy-Authentication-Info", 143 407, NE_PROXYAUTH, 144 N_("Could not authenticate to proxy server: %s") 145}; 146 147/* Authentication session state. */ 148typedef struct { 149 ne_session *sess; 150 151 /* Which context will auth challenges be accepted? */ 152 enum { 153 AUTH_ANY, /* ignore nothing. */ 154 AUTH_CONNECT, /* only in response to a CONNECT request. */ 155 AUTH_NOTCONNECT /* only in non-CONNECT responsees */ 156 } context; 157 158 /* Protocol type for server/proxy auth. */ 159 const struct auth_class *spec; 160 161 /* The protocol used for this authentication session */ 162 const struct auth_protocol *protocol; 163 164 struct auth_handler *handlers; 165 166 /*** Session details ***/ 167 168 /* The username and password we are using to authenticate with */ 169 char username[NE_ABUFSIZ]; 170 171 /* This used for Basic auth */ 172 char *basic; 173#ifdef HAVE_GSSAPI 174 /* for the GSSAPI/Negotiate scheme: */ 175 char *gssapi_token; 176 gss_ctx_id_t gssctx; 177 gss_name_t gssname; 178 gss_OID gssmech; 179#endif 180#ifdef HAVE_SSPI 181 /* This is used for SSPI (Negotiate/NTLM) auth */ 182 char *sspi_token; 183 void *sspi_context; 184#endif 185#ifdef HAVE_NTLM 186 /* This is used for NTLM auth */ 187 ne_ntlm_context *ntlm_context; 188#endif 189 /* These all used for Digest auth */ 190 char *realm; 191 char *nonce; 192 char *cnonce; 193 char *opaque; 194 char **domains; /* list of paths given as domain. */ 195 size_t ndomains; /* size of domains array */ 196 auth_qop qop; 197 auth_algorithm alg; 198 unsigned int nonce_count; 199 /* The ASCII representation of the session's H(A1) value */ 200 char h_a1[33]; 201 202 /* Temporary store for half of the Request-Digest 203 * (an optimisation - used in the response-digest calculation) */ 204 struct ne_md5_ctx *stored_rdig; 205} auth_session; 206 207struct auth_request { 208 /*** Per-request details. ***/ 209 ne_request *request; /* the request object. */ 210 211 /* The method and URI we are using for the current request */ 212 const char *uri; 213 const char *method; 214 215 int attempt; /* number of times this request has been retries due 216 * to auth challenges. */ 217}; 218 219/* Used if this protocol takes an unquoted non-name/value-pair 220 * parameter in the challenge. */ 221#define AUTH_FLAG_OPAQUE_PARAM (0x0001) 222/* Used if this Authentication-Info may be sent for non-40[17] 223 * response for this protocol. */ 224#define AUTH_FLAG_VERIFY_NON40x (0x0002) 225/* Used for broken the connection-based auth schemes. */ 226#define AUTH_FLAG_CONN_AUTH (0x0004) 227 228struct auth_protocol { 229 unsigned id; /* public NE_AUTH_* id. */ 230 231 int strength; /* protocol strength for sort order. */ 232 233 const char *name; /* protocol name. */ 234 235 /* Parse the authentication challenge; returns zero on success, or 236 * non-zero if this challenge be handled. 'attempt' is the number 237 * of times the request has been resent due to auth challenges. 238 * On failure, challenge_error() should be used to append an error 239 * message to the error buffer 'errmsg'. */ 240 int (*challenge)(auth_session *sess, int attempt, 241 struct auth_challenge *chall, ne_buffer **errmsg); 242 243 /* Return the string to send in the -Authenticate request header: 244 * (ne_malloc-allocated, NUL-terminated string) */ 245 char *(*response)(auth_session *sess, struct auth_request *req); 246 247 /* Parse a Authentication-Info response; returns NE_* error code 248 * on failure; on failure, the session error string must be 249 * set. */ 250 int (*verify)(struct auth_request *req, auth_session *sess, 251 const char *value); 252 253 int flags; /* AUTH_FLAG_* flags */ 254}; 255 256/* Helper function to append an error to the buffer during challenge 257 * handling. Pass printf-style string. *errmsg may be NULL and is 258 * allocated if necessary. errmsg must be non-NULL. */ 259static void challenge_error(ne_buffer **errmsg, const char *fmt, ...) 260 ne_attribute((format(printf, 2, 3))); 261 262/* Free the domains array, precondition sess->ndomains > 0. */ 263static void free_domains(auth_session *sess) 264{ 265 do { 266 ne_free(sess->domains[sess->ndomains - 1]); 267 } while (--sess->ndomains); 268 ne_free(sess->domains); 269 sess->domains = NULL; 270} 271 272static void clean_session(auth_session *sess) 273{ 274 if (sess->basic) ne_free(sess->basic); 275 if (sess->nonce) ne_free(sess->nonce); 276 if (sess->cnonce) ne_free(sess->cnonce); 277 if (sess->opaque) ne_free(sess->opaque); 278 if (sess->realm) ne_free(sess->realm); 279 sess->realm = sess->basic = sess->cnonce = sess->nonce = 280 sess->opaque = NULL; 281 if (sess->stored_rdig) { 282 ne_md5_destroy_ctx(sess->stored_rdig); 283 sess->stored_rdig = NULL; 284 } 285 if (sess->ndomains) free_domains(sess); 286#ifdef HAVE_GSSAPI 287 { 288 unsigned int major; 289 290 if (sess->gssctx != GSS_C_NO_CONTEXT) 291 gss_delete_sec_context(&major, &sess->gssctx, GSS_C_NO_BUFFER); 292 293 } 294 if (sess->gssapi_token) ne_free(sess->gssapi_token); 295 sess->gssapi_token = NULL; 296#endif 297#ifdef HAVE_SSPI 298 if (sess->sspi_token) ne_free(sess->sspi_token); 299 sess->sspi_token = NULL; 300 ne_sspi_destroy_context(sess->sspi_context); 301 sess->sspi_context = NULL; 302#endif 303#ifdef HAVE_NTLM 304 if (sess->ntlm_context) { 305 ne__ntlm_destroy_context(sess->ntlm_context); 306 sess->ntlm_context = NULL; 307 } 308#endif 309 310 sess->protocol = NULL; 311} 312 313/* Returns client nonce string. */ 314static char *get_cnonce(void) 315{ 316 char ret[33]; 317 unsigned char data[256]; 318 struct ne_md5_ctx *hash; 319 320 hash = ne_md5_create_ctx(); 321 322#ifdef HAVE_GNUTLS 323 if (1) { 324#if LIBGNUTLS_VERSION_NUMBER < 0x020b00 325 gcry_create_nonce(data, sizeof data); 326#else 327 gnutls_rnd(GNUTLS_RND_NONCE, data, sizeof data); 328#endif 329 ne_md5_process_bytes(data, sizeof data, hash); 330 } 331 else 332#elif defined(HAVE_OPENSSL) 333 if (RAND_status() == 1 && RAND_pseudo_bytes(data, sizeof data) >= 0) { 334 ne_md5_process_bytes(data, sizeof data, hash); 335 } 336 else 337#endif /* HAVE_OPENSSL */ 338 { 339 /* Fallback sources of random data: all bad, but no good sources 340 * are available. */ 341 342 /* Uninitialized stack data; yes, happy valgrinders, this is 343 * supposed to be here. */ 344 ne_md5_process_bytes(data, sizeof data, hash); 345 346 { 347#ifdef HAVE_GETTIMEOFDAY 348 struct timeval tv; 349 if (gettimeofday(&tv, NULL) == 0) 350 ne_md5_process_bytes(&tv, sizeof tv, hash); 351#else /* HAVE_GETTIMEOFDAY */ 352 time_t t = time(NULL); 353 ne_md5_process_bytes(&t, sizeof t, hash); 354#endif 355 } 356 { 357#ifdef WIN32 358 DWORD pid = GetCurrentThreadId(); 359#else 360 pid_t pid = getpid(); 361#endif 362 ne_md5_process_bytes(&pid, sizeof pid, hash); 363 } 364 } 365 366 ne_md5_finish_ascii(hash, ret); 367 ne_md5_destroy_ctx(hash); 368 369 return ne_strdup(ret); 370} 371 372/* Callback to retrieve user credentials for given session on given 373 * attempt (pre request) for given challenge. Password is written to 374 * pwbuf (of size NE_ABUFSIZ. On error, challenge_error() is used 375 * with errmsg. */ 376static int get_credentials(auth_session *sess, ne_buffer **errmsg, int attempt, 377 struct auth_challenge *chall, char *pwbuf) 378{ 379 if (chall->handler->creds(chall->handler->userdata, sess->realm, 380 chall->handler->attempt++, sess->username, pwbuf) == 0) { 381 return 0; 382 } else { 383 challenge_error(errmsg, _("rejected %s challenge"), 384 chall->protocol->name); 385 return -1; 386 } 387} 388 389/* Examine a Basic auth challenge. 390 * Returns 0 if an valid challenge, else non-zero. */ 391static int basic_challenge(auth_session *sess, int attempt, 392 struct auth_challenge *parms, 393 ne_buffer **errmsg) 394{ 395 char *tmp, password[NE_ABUFSIZ]; 396 397 /* Verify challenge... must have a realm */ 398 if (parms->realm == NULL) { 399 challenge_error(errmsg, _("missing realm in Basic challenge")); 400 return -1; 401 } 402 403 clean_session(sess); 404 405 sess->realm = ne_strdup(parms->realm); 406 407 if (get_credentials(sess, errmsg, attempt, parms, password)) { 408 /* Failed to get credentials */ 409 return -1; 410 } 411 412 tmp = ne_concat(sess->username, ":", password, NULL); 413 sess->basic = ne_base64((unsigned char *)tmp, strlen(tmp)); 414 ne_free(tmp); 415 416 /* Paranoia. */ 417 memset(password, 0, sizeof password); 418 419 return 0; 420} 421 422/* Add Basic authentication credentials to a request */ 423static char *request_basic(auth_session *sess, struct auth_request *req) 424{ 425 return ne_concat("Basic ", sess->basic, "\r\n", NULL); 426} 427 428#ifdef HAVE_GSSAPI 429/* Add GSSAPI authentication credentials to a request */ 430static char *request_negotiate(auth_session *sess, struct auth_request *req) 431{ 432 if (sess->gssapi_token) 433 return ne_concat("Negotiate ", sess->gssapi_token, "\r\n", NULL); 434 else 435 return NULL; 436} 437 438/* Create an GSSAPI name for server HOSTNAME; returns non-zero on 439 * error. */ 440static void get_gss_name(gss_name_t *server, const char *hostname) 441{ 442 unsigned int major, minor; 443 gss_buffer_desc token; 444 445 token.value = ne_concat("HTTP@", hostname, NULL); 446 token.length = strlen(token.value); 447 448 major = gss_import_name(&minor, &token, GSS_C_NT_HOSTBASED_SERVICE, 449 server); 450 ne_free(token.value); 451 452 if (GSS_ERROR(major)) { 453 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: gss_import_name failed.\n"); 454 *server = GSS_C_NO_NAME; 455 } 456} 457 458/* Append GSSAPI error(s) for STATUS of type TYPE to BUF; prepending 459 * ": " to each error if *FLAG is non-zero, setting *FLAG after an 460 * error has been appended. */ 461static void make_gss_error(ne_buffer *buf, int *flag, 462 unsigned int status, int type) 463{ 464 unsigned int major, minor; 465 unsigned int context = 0; 466 467 do { 468 gss_buffer_desc msg; 469 major = gss_display_status(&minor, status, type, 470 GSS_C_NO_OID, &context, &msg); 471 if (major == GSS_S_COMPLETE && msg.length) { 472 if ((*flag)++) ne_buffer_append(buf, ": ", 2); 473 ne_buffer_append(buf, msg.value, msg.length); 474 } 475 if (msg.length) gss_release_buffer(&minor, &msg); 476 } while (context); 477} 478 479/* Continue a GSS-API Negotiate exchange, using input TOKEN if 480 * non-NULL. Returns non-zero on error, in which case *errmsg is 481 * guaranteed to be non-NULL (i.e. an error message is set). */ 482static int continue_negotiate(auth_session *sess, const char *token, 483 ne_buffer **errmsg) 484{ 485 unsigned int major, minor; 486 gss_buffer_desc input = GSS_C_EMPTY_BUFFER; 487 gss_buffer_desc output = GSS_C_EMPTY_BUFFER; 488 unsigned char *bintoken = NULL; 489 int ret; 490 491 if (token) { 492 input.length = ne_unbase64(token, &bintoken); 493 if (input.length == 0) { 494 challenge_error(errmsg, _("invalid Negotiate token")); 495 return -1; 496 } 497 input.value = bintoken; 498 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Continuation token [%s]\n", token); 499 } 500 else if (sess->gssctx != GSS_C_NO_CONTEXT) { 501 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Reset incomplete context.\n"); 502 gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER); 503 } 504 505 major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &sess->gssctx, 506 sess->gssname, sess->gssmech, 507 GSS_C_MUTUAL_FLAG, GSS_C_INDEFINITE, 508 GSS_C_NO_CHANNEL_BINDINGS, 509 &input, &sess->gssmech, &output, NULL, NULL); 510 511 /* done with the input token. */ 512 if (bintoken) ne_free(bintoken); 513 514 if (GSS_ERROR(major)) { 515 int flag = 0; 516 517 challenge_error(errmsg, _("GSSAPI authentication error: ")); 518 make_gss_error(*errmsg, &flag, major, GSS_C_GSS_CODE); 519 make_gss_error(*errmsg, &flag, minor, GSS_C_MECH_CODE); 520 521 return -1; 522 } 523 524 if (major == GSS_S_CONTINUE_NEEDED || major == GSS_S_COMPLETE) { 525 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: init_sec_context OK. (major=%d)\n", 526 major); 527 ret = 0; 528 } 529 else { 530 challenge_error(errmsg, _("GSSAPI failure (code %u)"), major); 531 ret = -1; 532 } 533 534 if (major != GSS_S_CONTINUE_NEEDED) { 535 /* context no longer needed: destroy it */ 536 gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER); 537 } 538 539 if (output.length) { 540 sess->gssapi_token = ne_base64(output.value, output.length); 541 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Output token: [%s]\n", 542 sess->gssapi_token); 543 gss_release_buffer(&minor, &output); 544 } else { 545 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No output token.\n"); 546 } 547 548 return ret; 549} 550 551/* Process a Negotiate challange CHALL in session SESS; returns zero 552 * if challenge is accepted. */ 553static int negotiate_challenge(auth_session *sess, int attempt, 554 struct auth_challenge *chall, 555 ne_buffer **errmsg) 556{ 557 const char *token = chall->opaque; 558 559 /* Respect an initial challenge - which must have no input token, 560 * or a continuation - which must have an input token. */ 561 if (attempt == 0 || token) { 562 return continue_negotiate(sess, token, errmsg); 563 } 564 else { 565 challenge_error(errmsg, _("ignoring empty Negotiate continuation")); 566 return -1; 567 } 568} 569 570/* Verify the header HDR in a Negotiate response. */ 571static int verify_negotiate_response(struct auth_request *req, auth_session *sess, 572 const char *hdr) 573{ 574 char *duphdr = ne_strdup(hdr); 575 char *sep, *ptr = strchr(duphdr, ' '); 576 int ret; 577 ne_buffer *errmsg = NULL; 578 579 if (!ptr || strncmp(hdr, "Negotiate", ptr - duphdr) != 0) { 580 ne_set_error(sess->sess, _("Negotiate response verification failed: " 581 "invalid response header token")); 582 ne_free(duphdr); 583 return NE_ERROR; 584 } 585 586 ptr++; 587 588 if (strlen(ptr) == 0) { 589 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No token in Negotiate response!\n"); 590 ne_free(duphdr); 591 return NE_OK; 592 } 593 594 if ((sep = strchr(ptr, ',')) != NULL) 595 *sep = '\0'; 596 if ((sep = strchr(ptr, ' ')) != NULL) 597 *sep = '\0'; 598 599 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Negotiate response token [%s]\n", ptr); 600 ret = continue_negotiate(sess, ptr, &errmsg); 601 if (ret) { 602 ne_set_error(sess->sess, _("Negotiate response verification failure: %s"), 603 errmsg->data); 604 } 605 606 if (errmsg) ne_buffer_destroy(errmsg); 607 ne_free(duphdr); 608 609 return ret ? NE_ERROR : NE_OK; 610} 611#endif 612 613#ifdef HAVE_SSPI 614static char *request_sspi(auth_session *sess, struct auth_request *request) 615{ 616 if (sess->sspi_token) 617 return ne_concat(sess->protocol->name, " ", sess->sspi_token, "\r\n", NULL); 618 else 619 return NULL; 620} 621 622static int continue_sspi(auth_session *sess, int ntlm, const char *hdr) 623{ 624 int status; 625 char *response = NULL; 626 627 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: SSPI challenge.\n"); 628 629 if (!sess->sspi_context) { 630 ne_uri uri = {0}; 631 632 ne_fill_server_uri(sess->sess, &uri); 633 634 status = ne_sspi_create_context(&sess->sspi_context, uri.host, ntlm); 635 636 ne_uri_free(&uri); 637 638 if (status) { 639 return status; 640 } 641 } 642 643 status = ne_sspi_authenticate(sess->sspi_context, hdr, &response); 644 if (status) { 645 return status; 646 } 647 648 if (response && *response) { 649 sess->sspi_token = response; 650 651 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: SSPI challenge [%s]\n", sess->sspi_token); 652 } 653 654 return 0; 655} 656 657static int sspi_challenge(auth_session *sess, int attempt, 658 struct auth_challenge *parms, 659 ne_buffer **errmsg) 660{ 661 int ntlm = ne_strcasecmp(parms->protocol->name, "NTLM") == 0; 662 663 return continue_sspi(sess, ntlm, parms->opaque); 664} 665 666static int verify_sspi(struct auth_request *req, auth_session *sess, 667 const char *hdr) 668{ 669 int ntlm = ne_strncasecmp(hdr, "NTLM ", 5) == 0; 670 char *ptr = strchr(hdr, ' '); 671 672 if (!ptr) { 673 ne_set_error(sess->sess, _("SSPI response verification failed: " 674 "invalid response header token")); 675 return NE_ERROR; 676 } 677 678 while(*ptr == ' ') 679 ptr++; 680 681 if (*ptr == '\0') { 682 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: No token in SSPI response!\n"); 683 return NE_OK; 684 } 685 686 return continue_sspi(sess, ntlm, ptr); 687} 688 689#endif 690 691/* Parse the "domain" challenge parameter and set the domains array up 692 * in the session appropriately. */ 693static int parse_domain(auth_session *sess, const char *domain) 694{ 695 char *cp = ne_strdup(domain), *p = cp; 696 ne_uri base; 697 int invalid = 0; 698 699 memset(&base, 0, sizeof base); 700 ne_fill_server_uri(sess->sess, &base); 701 702 do { 703 char *token = ne_token(&p, ' '); 704 ne_uri rel, absolute; 705 706 if (ne_uri_parse(token, &rel) == 0) { 707 /* Resolve relative to the Request-URI. */ 708 base.path = "/"; 709 ne_uri_resolve(&base, &rel, &absolute); 710 711 /* Compare against the resolved path to check this URI has 712 * the same (scheme, host, port) components; ignore it 713 * otherwise: */ 714 base.path = absolute.path; 715 if (absolute.path && ne_uri_cmp(&absolute, &base) == 0) { 716 sess->domains = ne_realloc(sess->domains, 717 ++sess->ndomains * 718 sizeof(*sess->domains)); 719 sess->domains[sess->ndomains - 1] = absolute.path; 720 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Using domain %s from %s\n", 721 absolute.path, token); 722 absolute.path = NULL; 723 } 724 else { 725 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignoring domain %s\n", 726 token); 727 } 728 729 ne_uri_free(&absolute); 730 } 731 else { 732 invalid = 1; 733 } 734 735 ne_uri_free(&rel); 736 737 } while (p && !invalid); 738 739 if (invalid && sess->ndomains) { 740 free_domains(sess); 741 } 742 743 ne_free(cp); 744 base.path = NULL; 745 ne_uri_free(&base); 746 747 return invalid; 748} 749 750#ifdef HAVE_NTLM 751 752static char *request_ntlm(auth_session *sess, struct auth_request *request) 753{ 754 char *token = ne__ntlm_getRequestToken(sess->ntlm_context); 755 if (token) { 756 char *req = ne_concat(sess->protocol->name, " ", token, "\r\n", NULL); 757 ne_free(token); 758 return req; 759 } else { 760 return NULL; 761 } 762} 763 764static int ntlm_challenge(auth_session *sess, int attempt, 765 struct auth_challenge *parms, 766 ne_buffer **errmsg) 767{ 768 int status; 769 770 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: NTLM challenge.\n"); 771 772 if (!parms->opaque && (!sess->ntlm_context || (attempt > 1))) { 773 char password[NE_ABUFSIZ]; 774 775 if (get_credentials(sess, errmsg, attempt, parms, password)) { 776 /* Failed to get credentials */ 777 return -1; 778 } 779 780 if (sess->ntlm_context) { 781 ne__ntlm_destroy_context(sess->ntlm_context); 782 sess->ntlm_context = NULL; 783 } 784 785 sess->ntlm_context = ne__ntlm_create_context(sess->username, password); 786 } 787 788 status = ne__ntlm_authenticate(sess->ntlm_context, parms->opaque); 789 if (status) { 790 return status; 791 } 792 793 return 0; 794} 795#endif /* HAVE_NTLM */ 796 797/* Examine a digest challenge: return 0 if it is a valid Digest challenge, 798 * else non-zero. */ 799static int digest_challenge(auth_session *sess, int attempt, 800 struct auth_challenge *parms, 801 ne_buffer **errmsg) 802{ 803 char password[NE_ABUFSIZ]; 804 805 if (parms->alg == auth_alg_unknown) { 806 challenge_error(errmsg, _("unknown algorithm in Digest challenge")); 807 return -1; 808 } 809 else if (parms->alg == auth_alg_md5_sess && !parms->qop_auth) { 810 challenge_error(errmsg, _("incompatible algorithm in Digest challenge")); 811 return -1; 812 } 813 else if (parms->realm == NULL || parms->nonce == NULL) { 814 challenge_error(errmsg, _("missing parameter in Digest challenge")); 815 return -1; 816 } 817 else if (parms->stale && sess->realm == NULL) { 818 challenge_error(errmsg, _("initial Digest challenge was stale")); 819 return -1; 820 } 821 else if (parms->stale && (sess->alg != parms->alg 822 || strcmp(sess->realm, parms->realm))) { 823 /* With stale=true the realm and algorithm cannot change since these 824 * require re-hashing H(A1) which defeats the point. */ 825 challenge_error(errmsg, _("stale Digest challenge with new algorithm or realm")); 826 return -1; 827 } 828 829 if (!parms->stale) { 830 /* Non-stale challenge: clear session and request credentials. */ 831 clean_session(sess); 832 833 /* The domain paramater must be parsed after the session is 834 * cleaned; ignore domain for proxy auth. */ 835 if (parms->domain && sess->spec == &ah_server_class 836 && parse_domain(sess, parms->domain)) { 837 challenge_error(errmsg, _("could not parse domain in Digest challenge")); 838 return -1; 839 } 840 841 sess->realm = ne_strdup(parms->realm); 842 sess->alg = parms->alg; 843 sess->cnonce = get_cnonce(); 844 845 if (get_credentials(sess, errmsg, attempt, parms, password)) { 846 /* Failed to get credentials */ 847 return -1; 848 } 849 } 850 else { 851 /* Stale challenge: accept a new nonce or opaque. */ 852 if (sess->nonce) ne_free(sess->nonce); 853 if (sess->opaque && parms->opaque) ne_free(sess->opaque); 854 } 855 856 sess->nonce = ne_strdup(parms->nonce); 857 if (parms->opaque) { 858 sess->opaque = ne_strdup(parms->opaque); 859 } 860 861 if (parms->got_qop) { 862 /* What type of qop are we to apply to the message? */ 863 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got qop, using 2617-style.\n"); 864 sess->nonce_count = 0; 865 sess->qop = auth_qop_auth; 866 } else { 867 /* No qop at all/ */ 868 sess->qop = auth_qop_none; 869 } 870 871 if (!parms->stale) { 872 struct ne_md5_ctx *tmp; 873 874 /* Calculate H(A1). 875 * tmp = H(unq(username-value) ":" unq(realm-value) ":" passwd) 876 */ 877 tmp = ne_md5_create_ctx(); 878 ne_md5_process_bytes(sess->username, strlen(sess->username), tmp); 879 ne_md5_process_bytes(":", 1, tmp); 880 ne_md5_process_bytes(sess->realm, strlen(sess->realm), tmp); 881 ne_md5_process_bytes(":", 1, tmp); 882 ne_md5_process_bytes(password, strlen(password), tmp); 883 memset(password, 0, sizeof password); /* done with that. */ 884 if (sess->alg == auth_alg_md5_sess) { 885 struct ne_md5_ctx *a1; 886 char tmp_md5_ascii[33]; 887 888 /* Now we calculate the SESSION H(A1) 889 * A1 = H(...above...) ":" unq(nonce-value) ":" unq(cnonce-value) 890 */ 891 ne_md5_finish_ascii(tmp, tmp_md5_ascii); 892 a1 = ne_md5_create_ctx(); 893 ne_md5_process_bytes(tmp_md5_ascii, 32, a1); 894 ne_md5_process_bytes(":", 1, a1); 895 ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), a1); 896 ne_md5_process_bytes(":", 1, a1); 897 ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), a1); 898 ne_md5_finish_ascii(a1, sess->h_a1); 899 ne_md5_destroy_ctx(a1); 900 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Session H(A1) is [%s]\n", sess->h_a1); 901 } else { 902 ne_md5_finish_ascii(tmp, sess->h_a1); 903 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A1) is [%s]\n", sess->h_a1); 904 } 905 ne_md5_destroy_ctx(tmp); 906 907 } 908 909 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepting digest challenge.\n"); 910 911 return 0; 912} 913 914/* Returns non-zero if given Request-URI is inside the authentication 915 * domain defined for the session. */ 916static int inside_domain(auth_session *sess, const char *req_uri) 917{ 918 int inside = 0; 919 size_t n; 920 ne_uri uri; 921 922 /* Parse the Request-URI; it will be an absoluteURI if using a 923 * proxy, and possibly '*'. */ 924 if (strcmp(req_uri, "*") == 0 || ne_uri_parse(req_uri, &uri) != 0) { 925 /* Presume outside the authentication domain. */ 926 return 0; 927 } 928 929 for (n = 0; n < sess->ndomains && !inside; n++) { 930 const char *d = sess->domains[n]; 931 932 inside = strncmp(uri.path, d, strlen(d)) == 0; 933 } 934 935 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: '%s' is inside auth domain: %d.\n", 936 uri.path, inside); 937 ne_uri_free(&uri); 938 939 return inside; 940} 941 942/* Return Digest authentication credentials header value for the given 943 * session. */ 944static char *request_digest(auth_session *sess, struct auth_request *req) 945{ 946 struct ne_md5_ctx *a2, *rdig; 947 char a2_md5_ascii[33], rdig_md5_ascii[33]; 948 char nc_value[9] = {0}; 949 const char *qop_value = "auth"; /* qop-value */ 950 ne_buffer *ret; 951 952 /* Do not submit credentials if an auth domain is defined and this 953 * request-uri fails outside it. */ 954 if (sess->ndomains && !inside_domain(sess, req->uri)) { 955 return NULL; 956 } 957 958 /* Increase the nonce-count */ 959 if (sess->qop != auth_qop_none) { 960 sess->nonce_count++; 961 ne_snprintf(nc_value, 9, "%08x", sess->nonce_count); 962 } 963 964 /* Calculate H(A2). */ 965 a2 = ne_md5_create_ctx(); 966 ne_md5_process_bytes(req->method, strlen(req->method), a2); 967 ne_md5_process_bytes(":", 1, a2); 968 ne_md5_process_bytes(req->uri, strlen(req->uri), a2); 969 ne_md5_finish_ascii(a2, a2_md5_ascii); 970 ne_md5_destroy_ctx(a2); 971 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A2): %s\n", a2_md5_ascii); 972 973 /* Now, calculation of the Request-Digest. 974 * The first section is the regardless of qop value 975 * H(A1) ":" unq(nonce-value) ":" */ 976 rdig = ne_md5_create_ctx(); 977 978 /* Use the calculated H(A1) */ 979 ne_md5_process_bytes(sess->h_a1, 32, rdig); 980 981 ne_md5_process_bytes(":", 1, rdig); 982 ne_md5_process_bytes(sess->nonce, strlen(sess->nonce), rdig); 983 ne_md5_process_bytes(":", 1, rdig); 984 if (sess->qop != auth_qop_none) { 985 /* Add on: 986 * nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" 987 */ 988 ne_md5_process_bytes(nc_value, 8, rdig); 989 ne_md5_process_bytes(":", 1, rdig); 990 ne_md5_process_bytes(sess->cnonce, strlen(sess->cnonce), rdig); 991 ne_md5_process_bytes(":", 1, rdig); 992 /* Store a copy of this structure (see note below) */ 993 if (sess->stored_rdig) ne_md5_destroy_ctx(sess->stored_rdig); 994 sess->stored_rdig = ne_md5_dup_ctx(rdig); 995 ne_md5_process_bytes(qop_value, strlen(qop_value), rdig); 996 ne_md5_process_bytes(":", 1, rdig); 997 } 998 999 /* And finally, H(A2) */ 1000 ne_md5_process_bytes(a2_md5_ascii, 32, rdig); 1001 ne_md5_finish_ascii(rdig, rdig_md5_ascii); 1002 ne_md5_destroy_ctx(rdig); 1003 1004 ret = ne_buffer_create(); 1005 1006 ne_buffer_concat(ret, 1007 "Digest username=\"", sess->username, "\", " 1008 "realm=\"", sess->realm, "\", " 1009 "nonce=\"", sess->nonce, "\", " 1010 "uri=\"", req->uri, "\", " 1011 "response=\"", rdig_md5_ascii, "\", " 1012 "algorithm=\"", sess->alg == auth_alg_md5 ? "MD5" : "MD5-sess", "\"", 1013 NULL); 1014 1015 if (sess->opaque != NULL) { 1016 ne_buffer_concat(ret, ", opaque=\"", sess->opaque, "\"", NULL); 1017 } 1018 1019 if (sess->qop != auth_qop_none) { 1020 /* Add in cnonce and nc-value fields */ 1021 ne_buffer_concat(ret, ", cnonce=\"", sess->cnonce, "\", " 1022 "nc=", nc_value, ", " 1023 "qop=\"", qop_value, "\"", NULL); 1024 } 1025 1026 ne_buffer_zappend(ret, "\r\n"); 1027 1028 return ne_buffer_finish(ret); 1029} 1030 1031/* Parse line of comma-separated key-value pairs. If 'ischall' == 1, 1032 * then also return a leading space-separated token, as *value == 1033 * NULL. Otherwise, if return value is 0, *key and *value will be 1034 * non-NULL. If return value is non-zero, parsing has ended. If 1035 * 'sep' is non-NULL and ischall is 1, the separator character is 1036 * written to *sep when a challenge is parsed. */ 1037static int tokenize(char **hdr, char **key, char **value, char *sep, 1038 int ischall) 1039{ 1040 char *pnt = *hdr; 1041 enum { BEFORE_EQ, AFTER_EQ, AFTER_EQ_QUOTED } state = BEFORE_EQ; 1042 1043 if (**hdr == '\0') 1044 return 1; 1045 1046 *key = NULL; 1047 1048 do { 1049 switch (state) { 1050 case BEFORE_EQ: 1051 if (*pnt == '=') { 1052 if (*key == NULL) 1053 return -1; 1054 *pnt = '\0'; 1055 *value = pnt + 1; 1056 state = AFTER_EQ; 1057 } else if ((*pnt == ' ' || *pnt == ',') 1058 && ischall && *key != NULL) { 1059 *value = NULL; 1060 if (sep) *sep = *pnt; 1061 *pnt = '\0'; 1062 *hdr = pnt + 1; 1063 return 0; 1064 } else if (*key == NULL && strchr(" \r\n\t", *pnt) == NULL) { 1065 *key = pnt; 1066 } 1067 break; 1068 case AFTER_EQ: 1069 if (*pnt == ',') { 1070 *pnt = '\0'; 1071 *hdr = pnt + 1; 1072 return 0; 1073 } else if (*pnt == '\"') { 1074 state = AFTER_EQ_QUOTED; 1075 } 1076 break; 1077 case AFTER_EQ_QUOTED: 1078 if (*pnt == '\"') { 1079 state = AFTER_EQ; 1080 *pnt = '\0'; 1081 } 1082 break; 1083 } 1084 } while (*++pnt != '\0'); 1085 1086 if (state == BEFORE_EQ && ischall && *key != NULL) { 1087 *value = NULL; 1088 if (sep) *sep = '\0'; 1089 } 1090 1091 *hdr = pnt; 1092 1093 /* End of string: */ 1094 return 0; 1095} 1096 1097/* Pass this the value of the 'Authentication-Info:' header field, if 1098 * one is received. 1099 * Returns: 1100 * 0 if it gives a valid authentication for the server 1101 * non-zero otherwise (don't believe the response in this case!). 1102 */ 1103static int verify_digest_response(struct auth_request *req, auth_session *sess, 1104 const char *value) 1105{ 1106 char *hdr, *pnt, *key, *val; 1107 auth_qop qop = auth_qop_none; 1108 char *nextnonce, *rspauth, *cnonce, *nc, *qop_value; 1109 unsigned int nonce_count; 1110 int ret = NE_OK; 1111 1112 nextnonce = rspauth = cnonce = nc = qop_value = NULL; 1113 1114 pnt = hdr = ne_strdup(value); 1115 1116 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got Auth-Info header: %s\n", value); 1117 1118 while (tokenize(&pnt, &key, &val, NULL, 0) == 0) { 1119 val = ne_shave(val, "\""); 1120 1121 if (ne_strcasecmp(key, "qop") == 0) { 1122 qop_value = val; 1123 if (ne_strcasecmp(val, "auth") == 0) { 1124 qop = auth_qop_auth; 1125 } else { 1126 qop = auth_qop_none; 1127 } 1128 } else if (ne_strcasecmp(key, "nextnonce") == 0) { 1129 nextnonce = val; 1130 } else if (ne_strcasecmp(key, "rspauth") == 0) { 1131 rspauth = val; 1132 } else if (ne_strcasecmp(key, "cnonce") == 0) { 1133 cnonce = val; 1134 } else if (ne_strcasecmp(key, "nc") == 0) { 1135 nc = val; 1136 } 1137 } 1138 1139 if (qop == auth_qop_none) { 1140 /* The 2069-style A-I header only has the entity and nextnonce 1141 * parameters. */ 1142 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: 2069-style A-I header.\n"); 1143 } 1144 else if (!rspauth || !cnonce || !nc) { 1145 ret = NE_ERROR; 1146 ne_set_error(sess->sess, _("Digest mutual authentication failure: " 1147 "missing parameters")); 1148 } 1149 else if (strcmp(cnonce, sess->cnonce) != 0) { 1150 ret = NE_ERROR; 1151 ne_set_error(sess->sess, _("Digest mutual authentication failure: " 1152 "client nonce mismatch")); 1153 } 1154 else if (nc) { 1155 char *ptr; 1156 1157 errno = 0; 1158 nonce_count = strtoul(nc, &ptr, 16); 1159 if (*ptr != '\0' || errno) { 1160 ret = NE_ERROR; 1161 ne_set_error(sess->sess, _("Digest mutual authentication failure: " 1162 "could not parse nonce count")); 1163 } 1164 else if (nonce_count != sess->nonce_count) { 1165 ret = NE_ERROR; 1166 ne_set_error(sess->sess, _("Digest mutual authentication failure: " 1167 "nonce count mismatch (%u not %u)"), 1168 nonce_count, sess->nonce_count); 1169 } 1170 } 1171 1172 /* Finally, for qop=auth cases, if everything else is OK, verify 1173 * the response-digest field. */ 1174 if (qop == auth_qop_auth && ret == NE_OK) { 1175 struct ne_md5_ctx *a2; 1176 char a2_md5_ascii[33], rdig_md5_ascii[33]; 1177 1178 /* Modified H(A2): */ 1179 a2 = ne_md5_create_ctx(); 1180 ne_md5_process_bytes(":", 1, a2); 1181 ne_md5_process_bytes(req->uri, strlen(req->uri), a2); 1182 ne_md5_finish_ascii(a2, a2_md5_ascii); 1183 ne_md5_destroy_ctx(a2); 1184 1185 /* sess->stored_rdig contains digest-so-far of: 1186 * H(A1) ":" unq(nonce-value) 1187 */ 1188 1189 /* Add in qop-value */ 1190 ne_md5_process_bytes(qop_value, strlen(qop_value), 1191 sess->stored_rdig); 1192 ne_md5_process_bytes(":", 1, sess->stored_rdig); 1193 1194 /* Digest ":" H(A2) */ 1195 ne_md5_process_bytes(a2_md5_ascii, 32, sess->stored_rdig); 1196 /* All done */ 1197 ne_md5_finish_ascii(sess->stored_rdig, rdig_md5_ascii); 1198 ne_md5_destroy_ctx(sess->stored_rdig); 1199 sess->stored_rdig = NULL; 1200 1201 /* And... do they match? */ 1202 ret = ne_strcasecmp(rdig_md5_ascii, rspauth) == 0 ? NE_OK : NE_ERROR; 1203 1204 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: response-digest match: %s " 1205 "(expected [%s] vs actual [%s])\n", 1206 ret == NE_OK ? "yes" : "no", rdig_md5_ascii, rspauth); 1207 1208 if (ret) { 1209 ne_set_error(sess->sess, _("Digest mutual authentication failure: " 1210 "request-digest mismatch")); 1211 } 1212 } 1213 1214 /* Check for a nextnonce */ 1215 if (nextnonce != NULL) { 1216 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Found nextnonce of [%s].\n", nextnonce); 1217 ne_free(sess->nonce); 1218 sess->nonce = ne_strdup(nextnonce); 1219 sess->nonce_count = 0; 1220 } 1221 1222 ne_free(hdr); 1223 1224 return ret; 1225} 1226 1227static const struct auth_protocol protocols[] = { 1228 { NE_AUTH_BASIC, 10, "Basic", 1229 basic_challenge, request_basic, NULL, 1230 0 }, 1231 { NE_AUTH_DIGEST, 20, "Digest", 1232 digest_challenge, request_digest, verify_digest_response, 1233 0 }, 1234#ifdef HAVE_GSSAPI 1235 { NE_AUTH_GSSAPI, 30, "Negotiate", 1236 negotiate_challenge, request_negotiate, verify_negotiate_response, 1237 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH }, 1238#endif 1239#ifdef HAVE_SSPI 1240 { NE_AUTH_NTLM, 30, "NTLM", 1241 sspi_challenge, request_sspi, NULL, 1242 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH }, 1243 { NE_AUTH_GSSAPI, 30, "Negotiate", 1244 sspi_challenge, request_sspi, verify_sspi, 1245 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH }, 1246#endif 1247#ifdef HAVE_NTLM 1248 { NE_AUTH_NTLM, 30, "NTLM", 1249 ntlm_challenge, request_ntlm, NULL, 1250 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH }, 1251#endif 1252 { 0 } 1253}; 1254 1255/* Insert a new auth challenge for protocol 'proto' in list of 1256 * challenges 'list'. The challenge list is kept in sorted order of 1257 * strength, with highest strength first. */ 1258static struct auth_challenge *insert_challenge(struct auth_challenge **list, 1259 const struct auth_protocol *proto) 1260{ 1261 struct auth_challenge *ret = ne_calloc(sizeof *ret); 1262 struct auth_challenge *chall, *prev; 1263 1264 for (chall = *list, prev = NULL; chall != NULL; 1265 prev = chall, chall = chall->next) { 1266 if (proto->strength > chall->protocol->strength) { 1267 break; 1268 } 1269 } 1270 1271 if (prev) { 1272 ret->next = prev->next; 1273 prev->next = ret; 1274 } else { 1275 ret->next = *list; 1276 *list = ret; 1277 } 1278 1279 ret->protocol = proto; 1280 1281 return ret; 1282} 1283 1284static void challenge_error(ne_buffer **errbuf, const char *fmt, ...) 1285{ 1286 char err[128]; 1287 va_list ap; 1288 size_t len; 1289 1290 va_start(ap, fmt); 1291 len = ne_vsnprintf(err, sizeof err, fmt, ap); 1292 va_end(ap); 1293 1294 if (*errbuf == NULL) { 1295 *errbuf = ne_buffer_create(); 1296 ne_buffer_append(*errbuf, err, len); 1297 } 1298 else { 1299 ne_buffer_concat(*errbuf, ", ", err, NULL); 1300 } 1301} 1302 1303/* Passed the value of a "(Proxy,WWW)-Authenticate: " header field. 1304 * Returns 0 if valid challenge was accepted; non-zero if no valid 1305 * challenge was found. */ 1306static int auth_challenge(auth_session *sess, int attempt, 1307 const char *value) 1308{ 1309 char *pnt, *key, *val, *hdr, sep; 1310 struct auth_challenge *chall = NULL, *challenges = NULL; 1311 ne_buffer *errmsg = NULL; 1312 1313 pnt = hdr = ne_strdup(value); 1314 1315 /* The header value may be made up of one or more challenges. We 1316 * split it down into attribute-value pairs, then search for 1317 * schemes in the pair keys. */ 1318 1319 while (!tokenize(&pnt, &key, &val, &sep, 1)) { 1320 1321 if (val == NULL) { 1322 const struct auth_protocol *proto = NULL; 1323 struct auth_handler *hdl; 1324 size_t n; 1325 1326 for (hdl = sess->handlers; hdl; hdl = hdl->next) { 1327 for (n = 0; protocols[n].id; n++) { 1328 if (protocols[n].id & hdl->protomask 1329 && ne_strcasecmp(key, protocols[n].name) == 0) { 1330 proto = &protocols[n]; 1331 break; 1332 } 1333 } 1334 if (proto) break; 1335 } 1336 1337 if (proto == NULL) { 1338 /* Ignore this challenge. */ 1339 chall = NULL; 1340 challenge_error(&errmsg, _("ignored %s challenge"), key); 1341 continue; 1342 } 1343 1344 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got '%s' challenge.\n", proto->name); 1345 chall = insert_challenge(&challenges, proto); 1346 chall->handler = hdl; 1347 1348 if ((proto->flags & AUTH_FLAG_OPAQUE_PARAM) && sep == ' ') { 1349 /* Cope with the fact that the unquoted base64 1350 * paramater token doesn't match the 2617 auth-param 1351 * grammar: */ 1352 chall->opaque = ne_shave(ne_token(&pnt, ','), " \t"); 1353 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: %s opaque parameter '%s'\n", 1354 proto->name, chall->opaque); 1355 if (!pnt) break; /* stop parsing at end-of-string. */ 1356 } 1357 continue; 1358 } else if (chall == NULL) { 1359 /* Ignore pairs for an unknown challenge. */ 1360 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignored parameter: %s = %s\n", key, val); 1361 continue; 1362 } 1363 1364 /* Strip quotes off value. */ 1365 val = ne_shave(val, "\"'"); 1366 1367 if (ne_strcasecmp(key, "realm") == 0) { 1368 chall->realm = val; 1369 } else if (ne_strcasecmp(key, "nonce") == 0) { 1370 chall->nonce = val; 1371 } else if (ne_strcasecmp(key, "opaque") == 0) { 1372 chall->opaque = val; 1373 } else if (ne_strcasecmp(key, "stale") == 0) { 1374 /* Truth value */ 1375 chall->stale = (ne_strcasecmp(val, "true") == 0); 1376 } else if (ne_strcasecmp(key, "algorithm") == 0) { 1377 if (ne_strcasecmp(val, "md5") == 0) { 1378 chall->alg = auth_alg_md5; 1379 } else if (ne_strcasecmp(val, "md5-sess") == 0) { 1380 chall->alg = auth_alg_md5_sess; 1381 } else { 1382 chall->alg = auth_alg_unknown; 1383 } 1384 } else if (ne_strcasecmp(key, "qop") == 0) { 1385 /* iterate over each token in the value */ 1386 do { 1387 const char *tok = ne_shave(ne_token(&val, ','), " \t"); 1388 1389 if (ne_strcasecmp(tok, "auth") == 0) { 1390 chall->qop_auth = 1; 1391 } 1392 } while (val); 1393 1394 chall->got_qop = chall->qop_auth; 1395 } 1396 else if (ne_strcasecmp(key, "domain") == 0) { 1397 chall->domain = val; 1398 } 1399 } 1400 1401 sess->protocol = NULL; 1402 1403 /* Iterate through the challenge list (which is sorted from 1404 * strongest to weakest) attempting to accept each one. */ 1405 for (chall = challenges; chall != NULL; chall = chall->next) { 1406 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Trying %s challenge...\n", 1407 chall->protocol->name); 1408 if (chall->protocol->challenge(sess, attempt, chall, &errmsg) == 0) { 1409 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepted %s challenge.\n", 1410 chall->protocol->name); 1411 sess->protocol = chall->protocol; 1412 break; 1413 } 1414 } 1415 1416 if (!sess->protocol) { 1417 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: No challenges accepted.\n"); 1418 ne_set_error(sess->sess, _(sess->spec->error_noauth), 1419 errmsg ? errmsg->data : _("could not parse challenge")); 1420 } 1421 1422 while (challenges != NULL) { 1423 chall = challenges->next; 1424 ne_free(challenges); 1425 challenges = chall; 1426 } 1427 1428 ne_free(hdr); 1429 if (errmsg) ne_buffer_destroy(errmsg); 1430 1431 return !(sess->protocol != NULL); 1432} 1433 1434static void ah_create(ne_request *req, void *session, const char *method, 1435 const char *uri) 1436{ 1437 auth_session *sess = session; 1438 int is_connect = strcmp(method, "CONNECT") == 0; 1439 1440 if (sess->context == AUTH_ANY || 1441 (is_connect && sess->context == AUTH_CONNECT) || 1442 (!is_connect && sess->context == AUTH_NOTCONNECT)) { 1443 struct auth_request *areq = ne_calloc(sizeof *areq); 1444 struct auth_handler *hdl; 1445 1446 NE_DEBUG(NE_DBG_HTTPAUTH, "ah_create, for %s\n", sess->spec->resp_hdr); 1447 1448 areq->method = method; 1449 areq->uri = uri; 1450 areq->request = req; 1451 1452 ne_set_request_private(req, sess->spec->id, areq); 1453 1454 /* For each new request, reset the attempt counter in every 1455 * registered handler. */ 1456 for (hdl = sess->handlers; hdl; hdl = hdl->next) { 1457 hdl->attempt = 0; 1458 } 1459 } 1460} 1461 1462 1463static void ah_pre_send(ne_request *r, void *cookie, ne_buffer *request) 1464{ 1465 auth_session *sess = cookie; 1466 struct auth_request *req = ne_get_request_private(r, sess->spec->id); 1467 1468 if (sess->protocol && req) { 1469 char *value; 1470 1471 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Sending '%s' response.\n", 1472 sess->protocol->name); 1473 1474 value = sess->protocol->response(sess, req); 1475 1476 if (value != NULL) { 1477 ne_buffer_concat(request, sess->spec->req_hdr, ": ", value, NULL); 1478 ne_free(value); 1479 } 1480 } 1481 1482} 1483 1484static int ah_post_send(ne_request *req, void *cookie, const ne_status *status) 1485{ 1486 auth_session *sess = cookie; 1487 struct auth_request *areq = ne_get_request_private(req, sess->spec->id); 1488 const char *auth_hdr, *auth_info_hdr; 1489 int ret = NE_OK; 1490 1491 if (!areq) return NE_OK; 1492 1493 auth_hdr = ne_get_response_header(req, sess->spec->resp_hdr); 1494 auth_info_hdr = ne_get_response_header(req, sess->spec->resp_info_hdr); 1495 1496 if (sess->context == AUTH_CONNECT && status->code == 401 && !auth_hdr) { 1497 /* Some broken proxies issue a 401 as a proxy auth challenge 1498 * to a CONNECT request; handle this here. */ 1499 auth_hdr = ne_get_response_header(req, "WWW-Authenticate"); 1500 auth_info_hdr = NULL; 1501 } 1502 1503#ifdef HAVE_GSSAPI 1504 /* whatever happens: forget the GSSAPI token cached thus far */ 1505 if (sess->gssapi_token) { 1506 ne_free(sess->gssapi_token); 1507 sess->gssapi_token = NULL; 1508 } 1509#endif 1510 1511#ifdef HAVE_SSPI 1512 /* whatever happens: forget the SSPI token cached thus far */ 1513 if (sess->sspi_token) { 1514 ne_free(sess->sspi_token); 1515 sess->sspi_token = NULL; 1516 } 1517#endif 1518 1519 NE_DEBUG(NE_DBG_HTTPAUTH, 1520 "ah_post_send (#%d), code is %d (want %d), %s is %s\n", 1521 areq->attempt, status->code, sess->spec->status_code, 1522 sess->spec->resp_hdr, auth_hdr ? auth_hdr : "(none)"); 1523 if (auth_info_hdr && sess->protocol && sess->protocol->verify 1524 && (sess->protocol->flags & AUTH_FLAG_VERIFY_NON40x) == 0) { 1525 ret = sess->protocol->verify(areq, sess, auth_info_hdr); 1526 } 1527 else if (sess->protocol && sess->protocol->verify 1528 && (sess->protocol->flags & AUTH_FLAG_VERIFY_NON40x) 1529 && (status->klass == 2 || status->klass == 3) 1530 && auth_hdr) { 1531 ret = sess->protocol->verify(areq, sess, auth_hdr); 1532 } 1533 else if ((status->code == sess->spec->status_code || 1534 (status->code == 401 && sess->context == AUTH_CONNECT)) && 1535 auth_hdr) { 1536 /* note above: allow a 401 in response to a CONNECT request 1537 * from a proxy since some buggy proxies send that. */ 1538 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got challenge (code %d).\n", status->code); 1539 if (!auth_challenge(sess, areq->attempt++, auth_hdr)) { 1540 ret = NE_RETRY; 1541 } else { 1542 clean_session(sess); 1543 ret = sess->spec->fail_code; 1544 } 1545 1546 /* Set or clear the conn-auth flag according to whether this 1547 * was an accepted challenge for a borked protocol. */ 1548 ne_set_session_flag(sess->sess, NE_SESSFLAG_CONNAUTH, 1549 sess->protocol 1550 && (sess->protocol->flags & AUTH_FLAG_CONN_AUTH)); 1551 } 1552 1553#ifdef HAVE_SSPI 1554 /* Clear the SSPI context after successfull authentication. */ 1555 if ((status->klass == 2 || status->klass == 3) && sess->sspi_context) { 1556 ne_sspi_clear_context(sess->sspi_context); 1557 } 1558#endif 1559 1560 return ret; 1561} 1562 1563static void ah_destroy(ne_request *req, void *session) 1564{ 1565 auth_session *sess = session; 1566 struct auth_request *areq = ne_get_request_private(req, sess->spec->id); 1567 1568 if (areq) { 1569 ne_free(areq); 1570 } 1571} 1572 1573static void free_auth(void *cookie) 1574{ 1575 auth_session *sess = cookie; 1576 struct auth_handler *hdl, *next; 1577 1578#ifdef HAVE_GSSAPI 1579 if (sess->gssname != GSS_C_NO_NAME) { 1580 unsigned int major; 1581 gss_release_name(&major, &sess->gssname); 1582 } 1583#endif 1584 1585 for (hdl = sess->handlers; hdl; hdl = next) { 1586 next = hdl->next; 1587 ne_free(hdl); 1588 } 1589 1590 clean_session(sess); 1591 ne_free(sess); 1592} 1593 1594static void auth_register(ne_session *sess, int isproxy, unsigned protomask, 1595 const struct auth_class *ahc, const char *id, 1596 ne_auth_creds creds, void *userdata) 1597{ 1598 auth_session *ahs; 1599 struct auth_handler **hdl; 1600 1601 /* Handle the _ALL and _DEFAULT protocol masks: */ 1602 if (protomask == NE_AUTH_ALL) { 1603 protomask |= NE_AUTH_BASIC | NE_AUTH_DIGEST | NE_AUTH_NEGOTIATE; 1604 } 1605 else if (protomask == NE_AUTH_DEFAULT) { 1606 protomask |= NE_AUTH_BASIC | NE_AUTH_DIGEST; 1607 1608 if (strcmp(ne_get_scheme(sess), "https") == 0 || isproxy) { 1609 protomask |= NE_AUTH_NEGOTIATE; 1610 } 1611 } 1612 1613 if ((protomask & NE_AUTH_NEGOTIATE) == NE_AUTH_NEGOTIATE) { 1614 /* Map NEGOTIATE to NTLM | GSSAPI. */ 1615 protomask |= NE_AUTH_GSSAPI | NE_AUTH_NTLM; 1616 } 1617 1618 ahs = ne_get_session_private(sess, id); 1619 if (ahs == NULL) { 1620 ahs = ne_calloc(sizeof *ahs); 1621 1622 ahs->sess = sess; 1623 ahs->spec = ahc; 1624 1625 if (strcmp(ne_get_scheme(sess), "https") == 0) { 1626 ahs->context = isproxy ? AUTH_CONNECT : AUTH_NOTCONNECT; 1627 } else { 1628 ahs->context = AUTH_ANY; 1629 } 1630 1631 /* Register hooks */ 1632 ne_hook_create_request(sess, ah_create, ahs); 1633 ne_hook_pre_send(sess, ah_pre_send, ahs); 1634 ne_hook_post_send(sess, ah_post_send, ahs); 1635 ne_hook_destroy_request(sess, ah_destroy, ahs); 1636 ne_hook_destroy_session(sess, free_auth, ahs); 1637 1638 ne_set_session_private(sess, id, ahs); 1639 } 1640 1641#ifdef HAVE_GSSAPI 1642 if ((protomask & NE_AUTH_GSSAPI) && ahs->gssname == GSS_C_NO_NAME) { 1643 ne_uri uri = {0}; 1644 1645 if (isproxy) 1646 ne_fill_proxy_uri(sess, &uri); 1647 else 1648 ne_fill_server_uri(sess, &uri); 1649 1650 get_gss_name(&ahs->gssname, uri.host); 1651 1652 ne_uri_free(&uri); 1653 } 1654#endif 1655 1656 /* Find the end of the handler list, and add a new one. */ 1657 hdl = &ahs->handlers; 1658 while (*hdl) 1659 hdl = &(*hdl)->next; 1660 1661 *hdl = ne_malloc(sizeof **hdl); 1662 (*hdl)->protomask = protomask; 1663 (*hdl)->creds = creds; 1664 (*hdl)->userdata = userdata; 1665 (*hdl)->next = NULL; 1666 (*hdl)->attempt = 0; 1667} 1668 1669void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata) 1670{ 1671 auth_register(sess, 0, NE_AUTH_DEFAULT, &ah_server_class, HOOK_SERVER_ID, 1672 creds, userdata); 1673} 1674 1675void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata) 1676{ 1677 auth_register(sess, 1, NE_AUTH_DEFAULT, &ah_proxy_class, HOOK_PROXY_ID, 1678 creds, userdata); 1679} 1680 1681void ne_add_server_auth(ne_session *sess, unsigned protocol, 1682 ne_auth_creds creds, void *userdata) 1683{ 1684 auth_register(sess, 0, protocol, &ah_server_class, HOOK_SERVER_ID, 1685 creds, userdata); 1686} 1687 1688void ne_add_proxy_auth(ne_session *sess, unsigned protocol, 1689 ne_auth_creds creds, void *userdata) 1690{ 1691 auth_register(sess, 1, protocol, &ah_proxy_class, HOOK_PROXY_ID, 1692 creds, userdata); 1693} 1694 1695void ne_forget_auth(ne_session *sess) 1696{ 1697 auth_session *as; 1698 if ((as = ne_get_session_private(sess, HOOK_SERVER_ID)) != NULL) 1699 clean_session(as); 1700 if ((as = ne_get_session_private(sess, HOOK_PROXY_ID)) != NULL) 1701 clean_session(as); 1702} 1703 1704