1#include "server.h" 2#include "log.h" 3#include "http_auth.h" 4#include "inet_ntop_cache.h" 5#include "stream.h" 6#include "base64.h" 7 8#ifdef HAVE_CRYPT_H 9# include <crypt.h> 10#elif defined(__linux__) 11/* linux needs _XOPEN_SOURCE */ 12# define _XOPEN_SOURCE 13#endif 14 15#if defined(HAVE_LIBCRYPT) && !defined(HAVE_CRYPT) 16/* always assume crypt() is present if we have -lcrypt */ 17# define HAVE_CRYPT 18#endif 19 20#include <sys/types.h> 21#include <sys/stat.h> 22 23#include <fcntl.h> 24#include <stdlib.h> 25#include <stdio.h> 26#include <string.h> 27#include <time.h> 28#include <errno.h> 29#include <unistd.h> 30#include <ctype.h> 31 32#include "md5.h" 33 34#ifdef USE_OPENSSL 35#include <openssl/sha.h> 36#endif 37 38#include "safe_memclear.h" 39 40#define HASHLEN 16 41#define HASHHEXLEN 32 42typedef unsigned char HASH[HASHLEN]; 43typedef char HASHHEX[HASHHEXLEN+1]; 44 45static void CvtHex(const HASH Bin, char Hex[33]) { 46 li_tohex(Hex, (const char*) Bin, 16); 47} 48 49/** 50 * the $apr1$ handling is taken from apache 1.3.x 51 */ 52 53/* 54 * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0 55 * MD5 crypt() function, which is licenced as follows: 56 * ---------------------------------------------------------------------------- 57 * "THE BEER-WARE LICENSE" (Revision 42): 58 * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 59 * can do whatever you want with this stuff. If we meet some day, and you think 60 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 61 * ---------------------------------------------------------------------------- 62 */ 63 64handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s); 65 66static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) { 67 int ret = -1; 68 69 if (buffer_is_empty(username) || buffer_is_empty(realm)) return -1; 70 71 if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) { 72 stream f; 73 char * f_line; 74 75 if (buffer_string_is_empty(p->conf.auth_htdigest_userfile)) return -1; 76 77 if (0 != stream_open(&f, p->conf.auth_htdigest_userfile)) { 78 log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno)); 79 80 return -1; 81 } 82 83 f_line = f.start; 84 85 while (f_line - f.start != f.size) { 86 char *f_user, *f_pwd, *e, *f_realm; 87 size_t u_len, pwd_len, r_len; 88 89 f_user = f_line; 90 91 /* 92 * htdigest format 93 * 94 * user:realm:md5(user:realm:password) 95 */ 96 97 if (NULL == (f_realm = memchr(f_user, ':', f.size - (f_user - f.start) ))) { 98 log_error_write(srv, __FILE__, __LINE__, "sbs", 99 "parsed error in", p->conf.auth_htdigest_userfile, 100 "expected 'username:realm:hashed password'"); 101 102 stream_close(&f); 103 104 return -1; 105 } 106 107 if (NULL == (f_pwd = memchr(f_realm + 1, ':', f.size - (f_realm + 1 - f.start)))) { 108 log_error_write(srv, __FILE__, __LINE__, "sbs", 109 "parsed error in", p->conf.auth_plain_userfile, 110 "expected 'username:realm:hashed password'"); 111 112 stream_close(&f); 113 114 return -1; 115 } 116 117 /* get pointers to the fields */ 118 u_len = f_realm - f_user; 119 f_realm++; 120 r_len = f_pwd - f_realm; 121 f_pwd++; 122 123 if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) { 124 pwd_len = e - f_pwd; 125 } else { 126 pwd_len = f.size - (f_pwd - f.start); 127 } 128 129 if (buffer_string_length(username) == u_len && 130 (buffer_string_length(realm) == r_len) && 131 (0 == strncmp(username->ptr, f_user, u_len)) && 132 (0 == strncmp(realm->ptr, f_realm, r_len))) { 133 /* found */ 134 135 buffer_copy_string_len(password, f_pwd, pwd_len); 136 137 ret = 0; 138 break; 139 } 140 141 /* EOL */ 142 if (!e) break; 143 144 f_line = e + 1; 145 } 146 147 stream_close(&f); 148 } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD || 149 p->conf.auth_backend == AUTH_BACKEND_PLAIN) { 150 stream f; 151 char * f_line; 152 buffer *auth_fn; 153 154 auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile; 155 156 if (buffer_string_is_empty(auth_fn)) return -1; 157 158 if (0 != stream_open(&f, auth_fn)) { 159 log_error_write(srv, __FILE__, __LINE__, "sbss", 160 "opening plain-userfile", auth_fn, "failed:", strerror(errno)); 161 162 return -1; 163 } 164 165 f_line = f.start; 166 167 while (f_line - f.start != f.size) { 168 char *f_user, *f_pwd, *e; 169 size_t u_len, pwd_len; 170 171 f_user = f_line; 172 173 /* 174 * htpasswd format 175 * 176 * user:crypted passwd 177 */ 178 179 if (NULL == (f_pwd = memchr(f_user, ':', f.size - (f_user - f.start) ))) { 180 log_error_write(srv, __FILE__, __LINE__, "sbs", 181 "parsed error in", auth_fn, 182 "expected 'username:hashed password'"); 183 184 stream_close(&f); 185 186 return -1; 187 } 188 189 /* get pointers to the fields */ 190 u_len = f_pwd - f_user; 191 f_pwd++; 192 193 if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) { 194 pwd_len = e - f_pwd; 195 } else { 196 pwd_len = f.size - (f_pwd - f.start); 197 } 198 199 if (buffer_string_length(username) == u_len && 200 (0 == strncmp(username->ptr, f_user, u_len))) { 201 /* found */ 202 203 buffer_copy_string_len(password, f_pwd, pwd_len); 204 205 ret = 0; 206 break; 207 } 208 209 /* EOL */ 210 if (!e) break; 211 212 f_line = e + 1; 213 } 214 215 stream_close(&f); 216 } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) { 217 ret = 0; 218 } else { 219 return -1; 220 } 221 222 return ret; 223} 224 225int http_auth_match_rules(server *srv, array *req, const char *username, const char *group, const char *host) { 226 const char *r = NULL, *rules = NULL; 227 int username_len; 228 data_string *require; 229 230 UNUSED(group); 231 UNUSED(host); 232 233 require = (data_string *)array_get_element(req, "require"); 234 235 /* if we get here, the user we got a authed user */ 236 if (0 == strcmp(require->value->ptr, "valid-user")) { 237 return 0; 238 } 239 240 /* user=name1|group=name3|host=name4 */ 241 242 /* seperate the string by | */ 243#if 0 244 log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value); 245#endif 246 247 username_len = username ? strlen(username) : 0; 248 249 r = rules = require->value->ptr; 250 251 while (1) { 252 const char *eq; 253 const char *k, *v, *e; 254 int k_len, v_len, r_len; 255 256 e = strchr(r, '|'); 257 258 if (e) { 259 r_len = e - r; 260 } else { 261 r_len = strlen(rules) - (r - rules); 262 } 263 264 /* from r to r + r_len is a rule */ 265 266 if (0 == strncmp(r, "valid-user", r_len)) { 267 log_error_write(srv, __FILE__, __LINE__, "sb", 268 "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules", 269 require->value); 270 return -1; 271 } 272 273 /* search for = in the rules */ 274 if (NULL == (eq = strchr(r, '='))) { 275 log_error_write(srv, __FILE__, __LINE__, "sb", 276 "parsing the 'require' section in 'auth.require' failed: a = is missing", 277 require->value); 278 return -1; 279 } 280 281 /* = out of range */ 282 if (eq > r + r_len) { 283 log_error_write(srv, __FILE__, __LINE__, "sb", 284 "parsing the 'require' section in 'auth.require' failed: = out of range", 285 require->value); 286 287 return -1; 288 } 289 290 /* the part before the = is user|group|host */ 291 292 k = r; 293 k_len = eq - r; 294 v = eq + 1; 295 v_len = r_len - k_len - 1; 296 297 if (k_len == 4) { 298 if (0 == strncmp(k, "user", k_len)) { 299 if (username && 300 username_len == v_len && 301 0 == strncmp(username, v, v_len)) { 302 return 0; 303 } 304 } else if (0 == strncmp(k, "host", k_len)) { 305 log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)"); 306 } else { 307 log_error_write(srv, __FILE__, __LINE__, "s", "unknown key"); 308 return -1; 309 } 310 } else if (k_len == 5) { 311 if (0 == strncmp(k, "group", k_len)) { 312 log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)"); 313 } else { 314 log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k); 315 return -1; 316 } 317 } else { 318 log_error_write(srv, __FILE__, __LINE__, "s", "unknown key"); 319 return -1; 320 } 321 322 if (!e) break; 323 r = e + 1; 324 } 325 326 log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched"); 327 328 return -1; 329} 330 331#define APR_MD5_DIGESTSIZE 16 332#define APR1_ID "$apr1$" 333 334/* 335 * The following MD5 password encryption code was largely borrowed from 336 * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is 337 * licenced as stated at the top of this file. 338 */ 339 340static void to64(char *s, unsigned long v, int n) 341{ 342 static const unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */ 343 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 344 345 while (--n >= 0) { 346 *s++ = itoa64[v&0x3f]; 347 v >>= 6; 348 } 349} 350 351static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) { 352 /* 353 * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL, 354 * plus 4 for the '$' separators, plus the password hash itself. 355 * Let's leave a goodly amount of leeway. 356 */ 357 358 char passwd[120], *p; 359 const char *sp, *ep; 360 unsigned char final[APR_MD5_DIGESTSIZE]; 361 ssize_t sl, pl, i; 362 li_MD5_CTX ctx, ctx1; 363 unsigned long l; 364 365 /* 366 * Refine the salt first. It's possible we were given an already-hashed 367 * string as the salt argument, so extract the actual salt value from it 368 * if so. Otherwise just use the string up to the first '$' as the salt. 369 */ 370 sp = salt; 371 372 /* 373 * If it starts with the magic string, then skip that. 374 */ 375 if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) { 376 sp += strlen(APR1_ID); 377 } 378 379 /* 380 * It stops at the first '$' or 8 chars, whichever comes first 381 */ 382 for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) { 383 continue; 384 } 385 386 /* 387 * Get the length of the true salt 388 */ 389 sl = ep - sp; 390 391 /* 392 * 'Time to make the doughnuts..' 393 */ 394 li_MD5_Init(&ctx); 395 396 /* 397 * The password first, since that is what is most unknown 398 */ 399 li_MD5_Update(&ctx, pw, strlen(pw)); 400 401 /* 402 * Then our magic string 403 */ 404 li_MD5_Update(&ctx, APR1_ID, strlen(APR1_ID)); 405 406 /* 407 * Then the raw salt 408 */ 409 li_MD5_Update(&ctx, sp, sl); 410 411 /* 412 * Then just as many characters of the MD5(pw, salt, pw) 413 */ 414 li_MD5_Init(&ctx1); 415 li_MD5_Update(&ctx1, pw, strlen(pw)); 416 li_MD5_Update(&ctx1, sp, sl); 417 li_MD5_Update(&ctx1, pw, strlen(pw)); 418 li_MD5_Final(final, &ctx1); 419 for (pl = strlen(pw); pl > 0; pl -= APR_MD5_DIGESTSIZE) { 420 li_MD5_Update( 421 &ctx, final, 422 (pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl); 423 } 424 425 /* 426 * Don't leave anything around in vm they could use. 427 */ 428 memset(final, 0, sizeof(final)); 429 430 /* 431 * Then something really weird... 432 */ 433 for (i = strlen(pw); i != 0; i >>= 1) { 434 if (i & 1) { 435 li_MD5_Update(&ctx, final, 1); 436 } 437 else { 438 li_MD5_Update(&ctx, pw, 1); 439 } 440 } 441 442 /* 443 * Now make the output string. We know our limitations, so we 444 * can use the string routines without bounds checking. 445 */ 446 strcpy(passwd, APR1_ID); 447 strncat(passwd, sp, sl); 448 strcat(passwd, "$"); 449 450 li_MD5_Final(final, &ctx); 451 452 /* 453 * And now, just to make sure things don't run too fast.. 454 * On a 60 Mhz Pentium this takes 34 msec, so you would 455 * need 30 seconds to build a 1000 entry dictionary... 456 */ 457 for (i = 0; i < 1000; i++) { 458 li_MD5_Init(&ctx1); 459 if (i & 1) { 460 li_MD5_Update(&ctx1, pw, strlen(pw)); 461 } 462 else { 463 li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE); 464 } 465 if (i % 3) { 466 li_MD5_Update(&ctx1, sp, sl); 467 } 468 469 if (i % 7) { 470 li_MD5_Update(&ctx1, pw, strlen(pw)); 471 } 472 473 if (i & 1) { 474 li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE); 475 } 476 else { 477 li_MD5_Update(&ctx1, pw, strlen(pw)); 478 } 479 li_MD5_Final(final,&ctx1); 480 } 481 482 p = passwd + strlen(passwd); 483 484 l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4; 485 l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4; 486 l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4; 487 l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4; 488 l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4; 489 l = final[11] ; to64(p, l, 2); p += 2; 490 *p = '\0'; 491 492 /* 493 * Don't leave anything around in vm they could use. 494 */ 495 safe_memclear(final, sizeof(final)); 496 497 /* FIXME 498 */ 499#define apr_cpystrn strncpy 500 apr_cpystrn(result, passwd, nbytes - 1); 501} 502 503#ifdef USE_OPENSSL 504static void apr_sha_encode(const char *pw, char *result, size_t nbytes) { 505 unsigned char digest[20]; 506 size_t base64_written; 507 508 SHA1((const unsigned char*) pw, strlen(pw), digest); 509 510 memset(result, 0, nbytes); 511 512 /* need 5 bytes for "{SHA}", 28 for base64 (3 bytes -> 4 bytes) of SHA1 (20 bytes), 1 terminating */ 513 if (nbytes < 5 + 28 + 1) return; 514 515 memcpy(result, "{SHA}", 5); 516 base64_written = li_to_base64(result + 5, nbytes - 5, digest, 20, BASE64_STANDARD); 517 force_assert(base64_written == 28); 518 result[5 + base64_written] = '\0'; /* terminate string */ 519} 520#endif 521 522/** 523 * 524 * 525 * @param password password-string from the auth-backend 526 * @param pw password-string from the client 527 */ 528 529static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p, array *req, buffer *username, buffer *realm, buffer *password, const char *pw) { 530 UNUSED(srv); 531 UNUSED(req); 532 533 if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) { 534 /* 535 * htdigest format 536 * 537 * user:realm:md5(user:realm:password) 538 */ 539 540 li_MD5_CTX Md5Ctx; 541 HASH HA1; 542 char a1[256]; 543 544 li_MD5_Init(&Md5Ctx); 545 li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(username)); 546 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 547 li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(realm)); 548 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 549 li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw)); 550 li_MD5_Final(HA1, &Md5Ctx); 551 552 CvtHex(HA1, a1); 553 554 if (0 == strcmp(password->ptr, a1)) { 555 return 0; 556 } 557 } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) { 558 char sample[120]; 559 if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) { 560 /* 561 * The hash was created using $apr1$ custom algorithm. 562 */ 563 apr_md5_encode(pw, password->ptr, sample, sizeof(sample)); 564 return (strcmp(sample, password->ptr) == 0) ? 0 : 1; 565#ifdef USE_OPENSSL 566 } else if (0 == strncmp(password->ptr, "{SHA}", 5)) { 567 apr_sha_encode(pw, sample, sizeof(sample)); 568 return (strcmp(sample, password->ptr) == 0) ? 0 : 1; 569#endif 570 } else { 571#if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT) 572 char *crypted; 573#if defined(HAVE_CRYPT_R) 574 struct crypt_data crypt_tmp_data; 575 crypt_tmp_data.initialized = 0; 576#endif 577 578 /* a simple DES password is 2 + 11 characters. everything else should be longer. */ 579 if (buffer_string_length(password) < 13) { 580 return -1; 581 } 582 583#if defined(HAVE_CRYPT_R) 584 if (0 == (crypted = crypt_r(pw, password->ptr, &crypt_tmp_data))) { 585#else 586 if (0 == (crypted = crypt(pw, password->ptr))) { 587#endif 588 /* crypt failed. */ 589 return -1; 590 } 591 592 if (0 == strcmp(password->ptr, crypted)) { 593 return 0; 594 } 595#endif 596 } 597 } else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) { 598 if (0 == strcmp(password->ptr, pw)) { 599 return 0; 600 } 601 } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) { 602#ifdef USE_LDAP 603 LDAP *ldap; 604 LDAPMessage *lm, *first; 605 char *dn; 606 int ret; 607 char *attrs[] = { LDAP_NO_ATTRS, NULL }; 608 size_t i, len; 609 610 /* for now we stay synchronous */ 611 612 /* 613 * 1. connect anonymously (done in plugin init) 614 * 2. get DN for uid = username 615 * 3. auth against ldap server 616 * 4. (optional) check a field 617 * 5. disconnect 618 * 619 */ 620 621 /* check username 622 * 623 * we have to protect us againt username which modifies out filter in 624 * a unpleasant way 625 */ 626 627 len = buffer_string_length(username); 628 for (i = 0; i < len; i++) { 629 char c = username->ptr[i]; 630 631 if (!isalpha(c) && 632 !isdigit(c) && 633 (c != ' ') && 634 (c != '@') && 635 (c != '-') && 636 (c != '_') && 637 (c != '.') ) { 638 639 log_error_write(srv, __FILE__, __LINE__, "sbd", 640 "ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username, i); 641 642 return -1; 643 } 644 } 645 646 if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0') 647 return -1; 648 649 /* build filter */ 650 buffer_copy_buffer(p->ldap_filter, p->conf.ldap_filter_pre); 651 buffer_append_string_buffer(p->ldap_filter, username); 652 buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post); 653 654 655 /* 2. */ 656 if (p->anon_conf->ldap == NULL || 657 LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { 658 659 /* try again; the ldap library sometimes fails for the first call but reconnects */ 660 if (p->anon_conf->ldap == NULL || ret != LDAP_SERVER_DOWN || 661 LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { 662 663 if (auth_ldap_init(srv, p->anon_conf) != HANDLER_GO_ON) 664 return -1; 665 666 if (NULL == p->anon_conf->ldap) return -1; 667 668 if (LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { 669 log_error_write(srv, __FILE__, __LINE__, "sssb", 670 "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter); 671 return -1; 672 } 673 } 674 } 675 676 if (NULL == (first = ldap_first_entry(p->anon_conf->ldap, lm))) { 677 log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); 678 679 ldap_msgfree(lm); 680 681 return -1; 682 } 683 684 if (NULL == (dn = ldap_get_dn(p->anon_conf->ldap, first))) { 685 log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); 686 687 ldap_msgfree(lm); 688 689 return -1; 690 } 691 692 ldap_msgfree(lm); 693 694 695 /* 3. */ 696 if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) { 697 log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno)); 698 return -1; 699 } 700 701 ret = LDAP_VERSION3; 702 if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) { 703 log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); 704 705 ldap_unbind_s(ldap); 706 707 return -1; 708 } 709 710 if (p->conf.auth_ldap_starttls == 1) { 711 if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(ldap, NULL, NULL))) { 712 log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret)); 713 714 ldap_unbind_s(ldap); 715 716 return -1; 717 } 718 } 719 720 721 if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) { 722 log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); 723 724 ldap_unbind_s(ldap); 725 726 return -1; 727 } 728 729 /* 5. */ 730 ldap_unbind_s(ldap); 731 732 /* everything worked, good, access granted */ 733 734 return 0; 735#endif 736 } 737 return -1; 738} 739 740int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str) { 741 buffer *username, *password; 742 char *pw; 743 744 data_string *realm; 745 746 realm = (data_string *)array_get_element(req, "realm"); 747 748 username = buffer_init(); 749 750 if (!buffer_append_base64_decode(username, realm_str, strlen(realm_str), BASE64_STANDARD)) { 751 log_error_write(srv, __FILE__, __LINE__, "sb", "decodeing base64-string failed", username); 752 753 buffer_free(username); 754 return 0; 755 } 756 757 /* r2 == user:password */ 758 if (NULL == (pw = strchr(username->ptr, ':'))) { 759 log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username); 760 761 buffer_free(username); 762 return 0; 763 } 764 765 buffer_string_set_length(username, pw - username->ptr); 766 pw++; 767 768 password = buffer_init(); 769 /* copy password to r1 */ 770 if (http_auth_get_password(srv, p, username, realm->value, password)) { 771 buffer_free(username); 772 buffer_free(password); 773 774 if (AUTH_BACKEND_UNSET == p->conf.auth_backend) { 775 log_error_write(srv, __FILE__, __LINE__, "s", "auth.backend is not set"); 776 } else { 777 log_error_write(srv, __FILE__, __LINE__, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr))); 778 } 779 780 return 0; 781 } 782 783 /* password doesn't match */ 784 if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) { 785 log_error_write(srv, __FILE__, __LINE__, "sbsBss", "password doesn't match for", con->uri.path, "username:", username, ", IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr))); 786 787 buffer_free(username); 788 buffer_free(password); 789 790 return 0; 791 } 792 793 /* value is our allow-rules */ 794 if (http_auth_match_rules(srv, req, username->ptr, NULL, NULL)) { 795 buffer_free(username); 796 buffer_free(password); 797 798 log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match"); 799 800 return 0; 801 } 802 803 /* remember the username */ 804 buffer_copy_buffer(p->auth_user, username); 805 806 buffer_free(username); 807 buffer_free(password); 808 809 return 1; 810} 811 812typedef struct { 813 const char *key; 814 int key_len; 815 char **ptr; 816} digest_kv; 817 818/* return values: -1: error/bad request, 0: failed, 1: success */ 819int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str) { 820 char a1[256]; 821 char a2[256]; 822 823 char *username = NULL; 824 char *realm = NULL; 825 char *nonce = NULL; 826 char *uri = NULL; 827 char *algorithm = NULL; 828 char *qop = NULL; 829 char *cnonce = NULL; 830 char *nc = NULL; 831 char *respons = NULL; 832 833 char *e, *c; 834 const char *m = NULL; 835 int i; 836 buffer *password, *b, *username_buf, *realm_buf; 837 838 li_MD5_CTX Md5Ctx; 839 HASH HA1; 840 HASH HA2; 841 HASH RespHash; 842 HASHHEX HA2Hex; 843 844 845 /* init pointers */ 846#define S(x) \ 847 x, sizeof(x)-1, NULL 848 digest_kv dkv[10] = { 849 { S("username=") }, 850 { S("realm=") }, 851 { S("nonce=") }, 852 { S("uri=") }, 853 { S("algorithm=") }, 854 { S("qop=") }, 855 { S("cnonce=") }, 856 { S("nc=") }, 857 { S("response=") }, 858 859 { NULL, 0, NULL } 860 }; 861#undef S 862 863 dkv[0].ptr = &username; 864 dkv[1].ptr = &realm; 865 dkv[2].ptr = &nonce; 866 dkv[3].ptr = &uri; 867 dkv[4].ptr = &algorithm; 868 dkv[5].ptr = &qop; 869 dkv[6].ptr = &cnonce; 870 dkv[7].ptr = &nc; 871 dkv[8].ptr = &respons; 872 873 UNUSED(req); 874 875 if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST && 876 p->conf.auth_backend != AUTH_BACKEND_PLAIN) { 877 log_error_write(srv, __FILE__, __LINE__, "s", 878 "digest: unsupported backend (only htdigest or plain)"); 879 880 return -1; 881 } 882 883 b = buffer_init_string(realm_str); 884 885 /* parse credentials from client */ 886 for (c = b->ptr; *c; c++) { 887 /* skip whitespaces */ 888 while (*c == ' ' || *c == '\t') c++; 889 if (!*c) break; 890 891 for (i = 0; dkv[i].key; i++) { 892 if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) { 893 if ((c[dkv[i].key_len] == '"') && 894 (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) { 895 /* value with "..." */ 896 *(dkv[i].ptr) = c + dkv[i].key_len + 1; 897 c = e; 898 899 *e = '\0'; 900 } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) { 901 /* value without "...", terminated by ',' */ 902 *(dkv[i].ptr) = c + dkv[i].key_len; 903 c = e; 904 905 *e = '\0'; 906 } else { 907 /* value without "...", terminated by EOL */ 908 *(dkv[i].ptr) = c + dkv[i].key_len; 909 c += strlen(c) - 1; 910 } 911 } 912 } 913 } 914 915 if (p->conf.auth_debug > 1) { 916 log_error_write(srv, __FILE__, __LINE__, "ss", "username", username); 917 log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm); 918 log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce); 919 log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri); 920 log_error_write(srv, __FILE__, __LINE__, "ss", "algorithm", algorithm); 921 log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop); 922 log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce); 923 log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc); 924 log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons); 925 } 926 927 /* check if everything is transmitted */ 928 if (!username || 929 !realm || 930 !nonce || 931 !uri || 932 (qop && (!nc || !cnonce)) || 933 !respons ) { 934 /* missing field */ 935 936 log_error_write(srv, __FILE__, __LINE__, "s", 937 "digest: missing field"); 938 939 buffer_free(b); 940 return -1; 941 } 942 943 /** 944 * protect the md5-sess against missing cnonce and nonce 945 */ 946 if (algorithm && 947 0 == strcasecmp(algorithm, "md5-sess") && 948 (!nonce || !cnonce)) { 949 log_error_write(srv, __FILE__, __LINE__, "s", 950 "digest: (md5-sess: missing field"); 951 952 buffer_free(b); 953 return -1; 954 } 955 956 if (qop && strcasecmp(qop, "auth-int") == 0) { 957 log_error_write(srv, __FILE__, __LINE__, "s", 958 "digest: qop=auth-int not supported"); 959 960 buffer_free(b); 961 return -1; 962 } 963 964 m = get_http_method_name(con->request.http_method); 965 966 /* password-string == HA1 */ 967 password = buffer_init(); 968 username_buf = buffer_init_string(username); 969 realm_buf = buffer_init_string(realm); 970 if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) { 971 buffer_free(password); 972 buffer_free(b); 973 buffer_free(username_buf); 974 buffer_free(realm_buf); 975 return 0; 976 } 977 978 buffer_free(username_buf); 979 buffer_free(realm_buf); 980 981 if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) { 982 /* generate password from plain-text */ 983 li_MD5_Init(&Md5Ctx); 984 li_MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username)); 985 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 986 li_MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm)); 987 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 988 li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(password)); 989 li_MD5_Final(HA1, &Md5Ctx); 990 } else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) { 991 /* HA1 */ 992 /* transform the 32-byte-hex-md5 to a 16-byte-md5 */ 993 for (i = 0; i < HASHLEN; i++) { 994 HA1[i] = hex2int(password->ptr[i*2]) << 4; 995 HA1[i] |= hex2int(password->ptr[i*2+1]); 996 } 997 } else { 998 /* we already check that above */ 999 SEGFAULT(); 1000 } 1001 1002 buffer_free(password); 1003 1004 if (algorithm && 1005 strcasecmp(algorithm, "md5-sess") == 0) { 1006 li_MD5_Init(&Md5Ctx); 1007 /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */ 1008 CvtHex(HA1, a1); 1009 li_MD5_Update(&Md5Ctx, (unsigned char *)a1, 32); 1010 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1011 li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce)); 1012 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1013 li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce)); 1014 li_MD5_Final(HA1, &Md5Ctx); 1015 } 1016 1017 CvtHex(HA1, a1); 1018 1019 /* calculate H(A2) */ 1020 li_MD5_Init(&Md5Ctx); 1021 li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m)); 1022 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1023 li_MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri)); 1024 /* qop=auth-int not supported, already checked above */ 1025/* 1026 if (qop && strcasecmp(qop, "auth-int") == 0) { 1027 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1028 li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN); 1029 } 1030*/ 1031 li_MD5_Final(HA2, &Md5Ctx); 1032 CvtHex(HA2, HA2Hex); 1033 1034 /* calculate response */ 1035 li_MD5_Init(&Md5Ctx); 1036 li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN); 1037 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1038 li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce)); 1039 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1040 if (qop && *qop) { 1041 li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc)); 1042 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1043 li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce)); 1044 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1045 li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop)); 1046 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":")); 1047 }; 1048 li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN); 1049 li_MD5_Final(RespHash, &Md5Ctx); 1050 CvtHex(RespHash, a2); 1051 1052 if (0 != strcmp(a2, respons)) { 1053 /* digest not ok */ 1054 1055 if (p->conf.auth_debug) { 1056 log_error_write(srv, __FILE__, __LINE__, "sss", 1057 "digest: digest mismatch", a2, respons); 1058 } 1059 1060 log_error_write(srv, __FILE__, __LINE__, "ssss", 1061 "digest: auth failed for ", username, ": wrong password, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr))); 1062 1063 buffer_free(b); 1064 return 0; 1065 } 1066 1067 /* value is our allow-rules */ 1068 if (http_auth_match_rules(srv, req, username, NULL, NULL)) { 1069 buffer_free(b); 1070 1071 log_error_write(srv, __FILE__, __LINE__, "s", 1072 "digest: rules did match"); 1073 1074 return 0; 1075 } 1076 1077 /* remember the username */ 1078 buffer_copy_string(p->auth_user, username); 1079 1080 buffer_free(b); 1081 1082 if (p->conf.auth_debug) { 1083 log_error_write(srv, __FILE__, __LINE__, "s", 1084 "digest: auth ok"); 1085 } 1086 return 1; 1087} 1088 1089 1090int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char out[33]) { 1091 HASH h; 1092 li_MD5_CTX Md5Ctx; 1093 char hh[LI_ITOSTRING_LENGTH]; 1094 1095 UNUSED(p); 1096 1097 /* generate shared-secret */ 1098 li_MD5_Init(&Md5Ctx); 1099 li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(fn)); 1100 li_MD5_Update(&Md5Ctx, CONST_STR_LEN("+")); 1101 1102 /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */ 1103 li_itostr(hh, srv->cur_ts); 1104 li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); 1105 li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy)); 1106 li_itostr(hh, rand()); 1107 li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); 1108 1109 li_MD5_Final(h, &Md5Ctx); 1110 1111 CvtHex(h, out); 1112 1113 return 0; 1114} 1115