1 #include "first.h" 2 3 4 /*(htpasswd)*/ 5 #ifdef HAVE_CRYPT_H 6 # include <crypt.h> 7 #elif defined(__linux__) 8 /* linux needs _XOPEN_SOURCE */ 9 # define _XOPEN_SOURCE 10 #elif !defined(_MSC_VER) 11 #include <unistd.h> 12 #endif 13 14 #if defined(HAVE_LIBCRYPT) && !defined(HAVE_CRYPT) 15 /* always assume crypt() is present if we have -lcrypt */ 16 # define HAVE_CRYPT 17 #endif 18 19 #include <stdlib.h> 20 #include <string.h> 21 22 #include "mod_auth_api.h" 23 #include "sys-crypto-md.h" /* USE_LIB_CRYPTO */ 24 25 #include "base64.h" 26 #include "ck.h" 27 #include "fdevent.h" 28 #include "log.h" 29 #include "plugin.h" 30 #include "request.h" 31 32 /* 33 * htdigest, htpasswd, plain auth backends 34 */ 35 36 typedef struct { 37 const buffer *auth_plain_groupfile; 38 const buffer *auth_plain_userfile; 39 const buffer *auth_htdigest_userfile; 40 const buffer *auth_htpasswd_userfile; 41 } plugin_config; 42 43 typedef struct { 44 PLUGIN_DATA; 45 plugin_config defaults; 46 plugin_config conf; 47 } plugin_data; 48 49 static handler_t mod_authn_file_htdigest_digest(request_st *r, void *p_d, http_auth_info_t *ai); 50 static handler_t mod_authn_file_htdigest_basic(request_st *r, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); 51 static handler_t mod_authn_file_plain_digest(request_st *r, void *p_d, http_auth_info_t *ai); 52 static handler_t mod_authn_file_plain_basic(request_st *r, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); 53 static handler_t mod_authn_file_htpasswd_basic(request_st *r, void *p_d, const http_auth_require_t *require, const buffer *username, const char *pw); 54 55 INIT_FUNC(mod_authn_file_init) { 56 static http_auth_backend_t http_auth_backend_htdigest = 57 { "htdigest", mod_authn_file_htdigest_basic, mod_authn_file_htdigest_digest, NULL }; 58 static http_auth_backend_t http_auth_backend_htpasswd = 59 { "htpasswd", mod_authn_file_htpasswd_basic, NULL, NULL }; 60 static http_auth_backend_t http_auth_backend_plain = 61 { "plain", mod_authn_file_plain_basic, mod_authn_file_plain_digest, NULL }; 62 plugin_data *p = calloc(1, sizeof(*p)); 63 64 /* register http_auth_backend_htdigest */ 65 http_auth_backend_htdigest.p_d = p; 66 http_auth_backend_set(&http_auth_backend_htdigest); 67 68 /* register http_auth_backend_htpasswd */ 69 http_auth_backend_htpasswd.p_d = p; 70 http_auth_backend_set(&http_auth_backend_htpasswd); 71 72 /* register http_auth_backend_plain */ 73 http_auth_backend_plain.p_d = p; 74 http_auth_backend_set(&http_auth_backend_plain); 75 76 return p; 77 } 78 79 static void mod_authn_file_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 80 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 81 case 0: /* auth.backend.plain.groupfile */ 82 pconf->auth_plain_groupfile = cpv->v.b; 83 break; 84 case 1: /* auth.backend.plain.userfile */ 85 pconf->auth_plain_userfile = cpv->v.b; 86 break; 87 case 2: /* auth.backend.htdigest.userfile */ 88 pconf->auth_htdigest_userfile = cpv->v.b; 89 break; 90 case 3: /* auth.backend.htpasswd.userfile */ 91 pconf->auth_htpasswd_userfile = cpv->v.b; 92 break; 93 default:/* should not happen */ 94 return; 95 } 96 } 97 98 static void mod_authn_file_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 99 do { 100 mod_authn_file_merge_config_cpv(pconf, cpv); 101 } while ((++cpv)->k_id != -1); 102 } 103 104 static void mod_authn_file_patch_config(request_st * const r, plugin_data * const p) { 105 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 106 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 107 for (int i = 1, used = p->nconfig; i < used; ++i) { 108 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 109 mod_authn_file_merge_config(&p->conf, 110 p->cvlist + p->cvlist[i].v.u2[0]); 111 } 112 } 113 114 SETDEFAULTS_FUNC(mod_authn_file_set_defaults) { 115 static const config_plugin_keys_t cpk[] = { 116 { CONST_STR_LEN("auth.backend.plain.groupfile"), 117 T_CONFIG_STRING, 118 T_CONFIG_SCOPE_CONNECTION } 119 ,{ CONST_STR_LEN("auth.backend.plain.userfile"), 120 T_CONFIG_STRING, 121 T_CONFIG_SCOPE_CONNECTION } 122 ,{ CONST_STR_LEN("auth.backend.htdigest.userfile"), 123 T_CONFIG_STRING, 124 T_CONFIG_SCOPE_CONNECTION } 125 ,{ CONST_STR_LEN("auth.backend.htpasswd.userfile"), 126 T_CONFIG_STRING, 127 T_CONFIG_SCOPE_CONNECTION } 128 ,{ NULL, 0, 129 T_CONFIG_UNSET, 130 T_CONFIG_SCOPE_UNSET } 131 }; 132 133 plugin_data * const p = p_d; 134 if (!config_plugin_values_init(srv, p, cpk, "mod_authn_file")) 135 return HANDLER_ERROR; 136 137 /* process and validate config directives 138 * (init i to 0 if global context; to 1 to skip empty global context) */ 139 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 140 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 141 for (; -1 != cpv->k_id; ++cpv) { 142 switch (cpv->k_id) { 143 case 0: /* auth.backend.plain.groupfile */ 144 case 1: /* auth.backend.plain.userfile */ 145 case 2: /* auth.backend.htdigest.userfile */ 146 case 3: /* auth.backend.htpasswd.userfile */ 147 if (buffer_is_blank(cpv->v.b)) 148 cpv->v.b = NULL; 149 break; 150 default:/* should not happen */ 151 break; 152 } 153 } 154 } 155 156 /* initialize p->defaults from global config context */ 157 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 158 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 159 if (-1 != cpv->k_id) 160 mod_authn_file_merge_config(&p->defaults, cpv); 161 } 162 163 return HANDLER_GO_ON; 164 } 165 166 167 168 169 static void mod_authn_file_digest(http_auth_info_t *ai, const char *pw, size_t pwlen) { 170 171 li_md_iov_fn digest_iov = MD5_iov; 172 /* (ai->dalgo & HTTP_AUTH_DIGEST_MD5) default */ 173 #ifdef USE_LIB_CRYPTO 174 if (ai->dalgo & HTTP_AUTH_DIGEST_SHA256) 175 digest_iov = SHA256_iov; 176 #ifdef USE_LIB_CRYPTO_SHA512_256 177 else if (ai->dalgo & HTTP_AUTH_DIGEST_SHA512_256) 178 digest_iov = SHA512_256_iov; 179 #endif 180 #endif 181 182 struct const_iovec iov[] = { 183 { ai->username, ai->ulen } 184 ,{ ":", 1 } 185 ,{ ai->realm, ai->rlen } 186 ,{ ":", 1 } 187 ,{ pw, pwlen } 188 }; 189 digest_iov(ai->digest, iov, sizeof(iov)/sizeof(*iov)); 190 } 191 192 193 194 195 static int mod_authn_file_htdigest_get_loop(const char *data, const buffer *auth_fn, http_auth_info_t *ai, log_error_st *errh) { 196 const char *f_user = data, *n; 197 do { 198 n = strchr(f_user, '\n'); 199 /* (last line might not end in '\n') */ 200 if (NULL == n) n = f_user + strlen(f_user); 201 202 char *f_pwd, *f_realm; 203 size_t u_len, r_len; 204 205 /* skip blank lines and comment lines (beginning '#') */ 206 if (f_user[0] == '\n' || f_user[0] == '\r' || 207 f_user[0] == '#' || f_user[0] == '\0') continue; 208 /* skip excessively long lines */ 209 if (n - f_user > 1024) continue; 210 211 /* 212 * htdigest format 213 * 214 * (4th field for userhash is optional, 215 * though must be lowercase hex string if present) 216 * 217 * user:realm:<md5(user:realm:password)>:<md5(user:realm)> 218 * user:realm:<sha256(user:realm:password)>:<sha256(user:realm)> 219 */ 220 221 if (NULL == (f_realm = memchr(f_user, ':', n - f_user)) 222 || NULL == (f_pwd = memchr(f_realm+1, ':', n - (f_realm+1)))) { 223 log_error(errh, __FILE__, __LINE__, 224 "parse error in %s expected 'username:realm:digest[:userhash]'", 225 auth_fn->ptr); 226 continue; /* skip bad lines */ 227 } 228 229 /* get pointers to the fields */ 230 u_len = f_realm - f_user; 231 f_realm++; 232 r_len = f_pwd - f_realm; 233 f_pwd++; 234 const char *f_userhash = memchr(f_pwd, ':', (size_t)(n - f_pwd)); 235 236 if (ai->userhash) { 237 if (NULL == f_userhash) continue; 238 ++f_userhash; 239 size_t uh_len = n - f_userhash; 240 if (f_userhash[uh_len-1] == '\r') --uh_len; 241 if (ai->ulen == uh_len && ai->rlen == r_len 242 /*(timing-safe hash cmp might not matter much; do it anyway)*/ 243 /*&& 0 == memcmp(ai->username, f_userhash, uh_len)*/ 244 && ck_memeq_const_time_fixed_len(ai->username,f_userhash,uh_len) 245 && 0 == memcmp(ai->realm, f_realm, r_len) 246 && u_len <= sizeof(ai->userbuf)) { 247 /* found */ 248 ai->ulen = u_len; 249 ai->username = memcpy(ai->userbuf, f_user, u_len); 250 --f_userhash; /*(step back to ':' for pwd_len below)*/ 251 } 252 else 253 continue; 254 } 255 else 256 if (ai->ulen == u_len && ai->rlen == r_len 257 && 0 == memcmp(ai->username, f_user, u_len) 258 && 0 == memcmp(ai->realm, f_realm, r_len)) { 259 /* found */ 260 if (NULL == f_userhash) f_userhash = n; 261 } 262 else { 263 continue; 264 } 265 266 { 267 /* found */ 268 size_t pwd_len = f_userhash - f_pwd; 269 if (f_pwd[pwd_len-1] == '\r') --pwd_len; 270 271 if (pwd_len != (ai->dlen << 1)) continue; 272 return li_hex2bin(ai->digest, sizeof(ai->digest), f_pwd, pwd_len); 273 } 274 } while (*n && *(f_user = n+1)); 275 276 return -1; 277 } 278 279 static int mod_authn_file_htdigest_get(request_st * const r, void *p_d, http_auth_info_t * const ai) { 280 plugin_data *p = (plugin_data *)p_d; 281 mod_authn_file_patch_config(r, p); 282 const buffer * const auth_fn = p->conf.auth_htdigest_userfile; 283 if (!auth_fn) return -1; 284 285 off_t dlen = 64*1024*1024;/*(arbitrary limit: 64 MB file; expect < 1 MB)*/ 286 char *data = fdevent_load_file(auth_fn->ptr,&dlen,r->conf.errh,malloc,free); 287 if (NULL == data) return -1; 288 289 int rc = mod_authn_file_htdigest_get_loop(data, auth_fn, ai, r->conf.errh); 290 ck_memzero(data, (size_t)dlen); 291 free(data); 292 return rc; 293 } 294 295 static handler_t mod_authn_file_htdigest_digest(request_st * const r, void *p_d, http_auth_info_t * const ai) { 296 return (0 == mod_authn_file_htdigest_get(r, p_d, ai)) 297 ? HANDLER_GO_ON 298 : HANDLER_ERROR; 299 } 300 301 static handler_t mod_authn_file_htdigest_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) { 302 http_auth_info_t ai; 303 unsigned char htdigest[sizeof(ai.digest)]; 304 305 /* supports single choice of algorithm for digest stored in htdigest file */ 306 ai.dalgo = (require->algorithm & ~HTTP_AUTH_DIGEST_SESS); 307 ai.dlen = http_auth_digest_len(ai.dalgo); 308 ai.username = username->ptr; 309 ai.ulen = buffer_clen(username); 310 ai.realm = require->realm->ptr; 311 ai.rlen = buffer_clen(require->realm); 312 ai.userhash = 0; 313 314 if (mod_authn_file_htdigest_get(r, p_d, &ai)) return HANDLER_ERROR; 315 316 if (ai.dlen > sizeof(htdigest)) { 317 ck_memzero(ai.digest, ai.dlen); 318 return HANDLER_ERROR;/*(should not happen)*/ 319 } 320 memcpy(htdigest, ai.digest, ai.dlen); /*(save digest before reuse of ai)*/ 321 322 mod_authn_file_digest(&ai, pw, strlen(pw)); 323 324 int rc = (ck_memeq_const_time_fixed_len(htdigest, ai.digest, ai.dlen) 325 && http_auth_match_rules(require, username->ptr, NULL, NULL)); 326 327 ck_memzero(htdigest, ai.dlen); 328 ck_memzero(ai.digest, ai.dlen); 329 return rc ? HANDLER_GO_ON : HANDLER_ERROR; 330 } 331 332 333 334 335 static int mod_authn_file_htpasswd_get(const buffer *auth_fn, const char *username, size_t userlen, buffer *password, log_error_st *errh) { 336 if (NULL == username) return -1; 337 if (!auth_fn) return -1; 338 339 off_t dlen = 64*1024*1024;/*(arbitrary limit: 64 MB file; expect < 1 MB)*/ 340 char *data = fdevent_load_file(auth_fn->ptr, &dlen, errh, malloc, free); 341 if (NULL == data) return -1; 342 343 int rc = -1; 344 const char *f_user = data, *n; 345 do { 346 n = strchr(f_user, '\n'); 347 /* (last line might not end in '\n') */ 348 if (NULL == n) n = f_user + strlen(f_user); 349 350 char *f_pwd; 351 size_t u_len; 352 353 /* skip blank lines and comment lines (beginning '#') */ 354 if (f_user[0] == '\n' || f_user[0] == '\r' || 355 f_user[0] == '#' || f_user[0] == '\0') continue; 356 /* skip excessively long lines */ 357 if (n - f_user > 1024) continue; 358 359 /* 360 * htpasswd format 361 * 362 * user:crypted passwd 363 */ 364 365 if (NULL == (f_pwd = memchr(f_user, ':', n - f_user))) { 366 log_error(errh, __FILE__, __LINE__, 367 "parsed error in %s expected 'username:password'", 368 auth_fn->ptr); 369 continue; /* skip bad lines */ 370 } 371 372 /* get pointers to the fields */ 373 u_len = f_pwd - f_user; 374 f_pwd++; 375 376 if (userlen == u_len && 0 == memcmp(username, f_user, u_len)) { 377 /* found */ 378 379 size_t pwd_len = n - f_pwd; 380 if (f_pwd[pwd_len-1] == '\r') --pwd_len; 381 382 buffer_copy_string_len(password, f_pwd, pwd_len); 383 384 rc = 0; 385 break; 386 } 387 } while (*n && *(f_user = n+1)); 388 389 ck_memzero(data, (size_t)dlen); 390 free(data); 391 return rc; 392 } 393 394 static handler_t mod_authn_file_plain_digest(request_st * const r, void *p_d, http_auth_info_t * const ai) { 395 plugin_data *p = (plugin_data *)p_d; 396 mod_authn_file_patch_config(r, p); 397 buffer * const tb = r->tmp_buf; /* password-string from auth-backend */ 398 int rc = mod_authn_file_htpasswd_get(p->conf.auth_plain_userfile, 399 ai->username, ai->ulen, tb, 400 r->conf.errh); 401 if (0 != rc) return HANDLER_ERROR; 402 403 /* generate password digest from plain-text */ 404 mod_authn_file_digest(ai, BUF_PTR_LEN(tb)); 405 size_t tblen = (buffer_clen(tb) + 63) & ~63u; 406 buffer_clear(tb); 407 ck_memzero(tb->ptr, tblen < tb->size ? tblen : tb->size); 408 return HANDLER_GO_ON; 409 } 410 411 static handler_t mod_authn_file_plain_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) { 412 plugin_data *p = (plugin_data *)p_d; 413 mod_authn_file_patch_config(r, p); 414 buffer * const tb = r->tmp_buf; /* password-string from auth-backend */ 415 int rc = mod_authn_file_htpasswd_get(p->conf.auth_plain_userfile, 416 BUF_PTR_LEN(username), tb, 417 r->conf.errh); 418 if (0 == rc) { 419 rc = ck_memeq_const_time(BUF_PTR_LEN(tb), pw, strlen(pw)) ? 0 : -1; 420 size_t tblen = (buffer_clen(tb) + 63) & ~63u; 421 buffer_clear(tb); 422 ck_memzero(tb->ptr, tblen < tb->size ? tblen : tb->size); 423 } 424 return 0 == rc && http_auth_match_rules(require, username->ptr, NULL, NULL) 425 ? HANDLER_GO_ON 426 : HANDLER_ERROR; 427 } 428 429 430 431 432 /** 433 * the $apr1$ handling is taken from apache 1.3.x 434 * XXX: code has since been modified for slightly better performance 435 */ 436 437 /* 438 * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0 439 * MD5 crypt() function, which is licenced as follows: 440 * ---------------------------------------------------------------------------- 441 * "THE BEER-WARE LICENSE" (Revision 42): 442 * <[email protected]> wrote this file. As long as you retain this notice you 443 * can do whatever you want with this stuff. If we meet some day, and you think 444 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 445 * ---------------------------------------------------------------------------- 446 */ 447 448 #define APR_MD5_DIGESTSIZE 16 449 #define APR1_ID "$apr1$" 450 451 /* 452 * The following MD5 password encryption code was largely borrowed from 453 * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is 454 * licenced as stated above. 455 */ 456 457 static void to64(char *s, unsigned long v, int n) 458 { 459 static const unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */ 460 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 461 462 while (--n >= 0) { 463 *s++ = itoa64[v&0x3f]; 464 v >>= 6; 465 } 466 } 467 468 static size_t apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) { 469 force_assert(nbytes >= 37); /*(nbytes should be >= 37)*/ 470 471 const size_t pwlen = strlen(pw); 472 ssize_t sl; 473 474 /* 475 * Refine the salt first. It's possible we were given an already-hashed 476 * string as the salt argument, so extract the actual salt value from it 477 * if so. Otherwise just use the string up to the first '$' as the salt. 478 */ 479 480 #if 0 /*(already checked and stepped-over in caller)*/ 481 /* 482 * If it starts with the magic string, then skip that. 483 */ 484 if (!strncmp(salt, APR1_ID, sizeof(APR1_ID)-1)) { 485 salt += sizeof(APR1_ID)-1; 486 } 487 #endif 488 489 /* 490 * Get the length of the true salt 491 */ 492 /* 493 * It stops at the first '$' or 8 chars, whichever comes first 494 */ 495 for (sl = 0; sl < 8 && salt[sl] != '$' && salt[sl] != '\0'; ++sl) ; 496 497 /* result begins with "$apr1$salt$" */ 498 memcpy(result, APR1_ID, sizeof(APR1_ID)-1); 499 memcpy(result+sizeof(APR1_ID)-1, salt, sl); 500 result[sizeof(APR1_ID)-1+sl] = '$'; 501 502 MD5_CTX ctx; 503 unsigned char final[APR_MD5_DIGESTSIZE]; 504 505 MD5_Init(&ctx); 506 MD5_Update(&ctx, pw, pwlen); 507 MD5_Update(&ctx, salt, sl); 508 MD5_Update(&ctx, pw, pwlen); 509 MD5_Final(final, &ctx); 510 511 /* 512 * 'Time to make the doughnuts..' 513 */ 514 MD5_Init(&ctx); 515 516 /* 517 * The password first, since that is what is most unknown 518 */ 519 MD5_Update(&ctx, pw, pwlen); 520 521 #if 0 522 /* 523 * Then our magic string 524 */ 525 MD5_Update(&ctx, APR1_ID, sizeof(APR1_ID)-1); 526 527 /* 528 * Then the raw salt 529 */ 530 MD5_Update(&ctx, salt, sl); 531 #else 532 MD5_Update(&ctx, result, sizeof(APR1_ID)-1 + sl); 533 #endif 534 535 /* 536 * Then just as many characters of the MD5(pw, salt, pw) 537 */ 538 for (ssize_t pl = pwlen; pl > 0; pl -= APR_MD5_DIGESTSIZE) { 539 MD5_Update(&ctx, final, 540 (pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl); 541 } 542 543 /* 544 * Don't leave anything around in vm they could use. 545 */ 546 /*ck_memzero(final, sizeof(final));*/ 547 final[0] = 0; /*(preserve behavior for loop below)*/ 548 549 /* 550 * Then something really weird... 551 */ 552 for (size_t i = pwlen; i != 0; i >>= 1) { 553 MD5_Update(&ctx, (i & 1) ? (char *)final : pw, 1); 554 } 555 MD5_Final(final, &ctx); 556 557 /* 558 * And now, just to make sure things don't run too fast.. 559 * On a 60 Mhz Pentium this takes 34 msec, so you would 560 * need 30 seconds to build a 1000 entry dictionary... 561 */ 562 for (int i = 0; i < 1000; ++i) { 563 MD5_Init(&ctx); 564 if (i & 1) { 565 MD5_Update(&ctx, pw, pwlen); 566 } 567 else { 568 MD5_Update(&ctx, final, APR_MD5_DIGESTSIZE); 569 } 570 if (i % 3) { 571 MD5_Update(&ctx, salt, sl); 572 } 573 574 if (i % 7) { 575 MD5_Update(&ctx, pw, pwlen); 576 } 577 578 if (i & 1) { 579 MD5_Update(&ctx, final, APR_MD5_DIGESTSIZE); 580 } 581 else { 582 MD5_Update(&ctx, pw, pwlen); 583 } 584 MD5_Final(final,&ctx); 585 } 586 587 /* 588 * Now make the output string. (nbytes checked at top of func) 589 * Maximum result size below is 37: 590 * 6 for APR_ID, <= 8 for salt, 1 for '$', 22 for password hash 591 */ 592 593 result += sizeof(APR1_ID)-1 + sl + 1; 594 to64(result, (final[ 0]<<16) | (final[ 6]<<8) | final[12], 4); 595 to64(result+4, (final[ 1]<<16) | (final[ 7]<<8) | final[13], 4); 596 to64(result+8, (final[ 2]<<16) | (final[ 8]<<8) | final[14], 4); 597 to64(result+12, (final[ 3]<<16) | (final[ 9]<<8) | final[15], 4); 598 to64(result+16, (final[ 4]<<16) | (final[10]<<8) | final[ 5], 4); 599 to64(result+20, final[11] , 2); 600 601 /* 602 * Don't leave anything around in vm they could use. 603 */ 604 ck_memzero(final, sizeof(final)); 605 return (sizeof(APR1_ID)-1 + sl + 1 + 22); 606 } 607 608 #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT) 609 static int mod_authn_file_crypt_cmp(const buffer * const password, const char * const pw) { 610 int rc = -1; 611 char *crypted = NULL; 612 #if 0 && defined(HAVE_CRYPT_R) 613 struct crypt_data crypt_tmp_data; 614 #ifdef _AIX 615 memset(&crypt_tmp_data, 0, sizeof(crypt_tmp_data)); 616 #else 617 crypt_tmp_data.initialized = 0; 618 #endif 619 #endif 620 #ifdef USE_LIB_CRYPTO_MD4 /*(for MD4_*() (e.g. MD4_Update()))*/ 621 /*(caller checked buffer_clen(passwd) >= 13)*/ 622 if (0 == memcmp(password->ptr, CONST_STR_LEN("$1+ntlm$"))) { 623 /* CRYPT-MD5-NTLM algorithm 624 * This algorithm allows for the construction of (slightly more) 625 * secure, salted password hashes from an environment where only 626 * legacy NTLM hashes are available and where it is not feasible 627 * to re-hash all the passwords with the MD5-based crypt(). */ 628 /* Note: originally, LM password were limited to 14 chars. 629 * NTLM passwords limited to 127 chars, and encoding to UCS-2LE 630 * requires double that, so sample[256] buf is large enough. 631 * Prior sample[120] size likely taken from apr_md5_encode(). */ 632 char sample[256]; 633 char *b = password->ptr+sizeof("$1+ntlm$")-1; 634 char *e = strchr(b, '$'); 635 size_t slen = (NULL != e) ? (size_t)(e - b) : sizeof(sample); 636 size_t pwlen = strlen(pw) * 2; 637 if (slen < sizeof(sample) - (sizeof("$1$")-1) 638 && pwlen < sizeof(sample)) { 639 /* compute NTLM hash and convert to lowercase hex chars 640 * (require lc hex chars from li_tohex()) */ 641 if (pwlen) { 642 /*(reuse sample buffer to encode pw into UCS-2LE) 643 *(Note: assumes pw input in ISO-8859-1) */ 644 /*(buffer sizes checked above)*/ 645 for (int i=0; i < (int)pwlen; i+=2) { 646 sample[i] = pw[(i >> 1)]; 647 sample[i+1] = 0; 648 } 649 } 650 char ntlmhash[MD4_DIGEST_LENGTH]; 651 char ntlmhex[MD4_DIGEST_LENGTH*2+1]; 652 MD4_once((unsigned char *)ntlmhash, sample, pwlen); 653 li_tohex(ntlmhex,sizeof(ntlmhex),ntlmhash,sizeof(ntlmhash)); 654 655 /*(reuse sample buffer for salt (FYI: expect slen == 8))*/ 656 memcpy(sample, "$1$", sizeof("$1$")-1); 657 memcpy(sample+sizeof("$1$")-1, b, slen); 658 sample[sizeof("$1$")-1+slen] = '\0'; 659 #if 0 && defined(HAVE_CRYPT_R) 660 crypted = crypt_r(ntlmhex, sample, &crypt_tmp_data); 661 #else 662 crypted = crypt(ntlmhex, sample); 663 #endif 664 if (NULL != crypted 665 && 0 == strncmp(crypted, "$1$", sizeof("$1$")-1)) { 666 rc = strcmp(b, crypted+3); /*skip crypted "$1$" prefix*/ 667 } 668 ck_memzero(sample, sizeof(sample)); 669 } 670 } 671 else 672 #endif 673 { 674 #if 0 && defined(HAVE_CRYPT_R) 675 crypted = crypt_r(pw, password->ptr, &crypt_tmp_data); 676 #else 677 crypted = crypt(pw, password->ptr); 678 #endif 679 if (NULL != crypted) { 680 rc = strcmp(password->ptr, crypted); 681 } 682 } 683 if (NULL != crypted) { 684 size_t crypwlen = strlen(crypted); 685 if (crypwlen >= 13) ck_memzero(crypted, crypwlen); 686 } 687 return rc; 688 } 689 #endif 690 691 static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) { 692 plugin_data *p = (plugin_data *)p_d; 693 mod_authn_file_patch_config(r, p); 694 buffer * const tb = r->tmp_buf; /* password-string from auth-backend */ 695 int rc = mod_authn_file_htpasswd_get(p->conf.auth_htpasswd_userfile, 696 BUF_PTR_LEN(username), tb, 697 r->conf.errh); 698 if (0 != rc) return HANDLER_ERROR; 699 700 uint32_t tblen = buffer_clen(tb); 701 rc = -1; 702 if (tblen >= 5 && 0 == memcmp(tb->ptr, "{SHA}", 5)) { 703 /* 32 == (5 for "{SHA}" + 28 for base64 of SHA1 (20 bytes)) */ 704 unsigned char digest[SHA_DIGEST_LENGTH*2]; 705 SHA1_once(digest+SHA_DIGEST_LENGTH, pw, strlen(pw)); 706 rc = SHA_DIGEST_LENGTH 707 == li_base64_dec(digest, sizeof(digest), 708 tb->ptr+5, tblen-5, BASE64_STANDARD) 709 && ck_memeq_const_time_fixed_len(digest, digest+SHA_DIGEST_LENGTH, 710 SHA_DIGEST_LENGTH); 711 rc = !rc; /* (0 == rc) for match */ 712 ck_memzero(digest, sizeof(digest)); 713 } 714 else if (tblen >= 6 && 0 == memcmp(tb->ptr, "$apr1$", 6)) { 715 char sample[40]; /*(see comments at end of apr_md5_encode())*/ 716 rc = tblen == apr_md5_encode(pw, tb->ptr+6, sample, sizeof(sample)) 717 && ck_memeq_const_time_fixed_len(sample, tb->ptr, tblen); 718 rc = !rc; /* (0 == rc) for match */ 719 ck_memzero(sample, sizeof(sample)); 720 } 721 #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT) 722 /* simple DES password is 2 + 11 characters; 723 * everything else should be longer */ 724 else if (tblen >= 13) { 725 rc = mod_authn_file_crypt_cmp(tb, pw); 726 } 727 #endif 728 tblen = (tblen + 63) & ~63u; 729 buffer_clear(tb); 730 ck_memzero(tb->ptr, tblen < tb->size ? tblen : tb->size); 731 return 0 == rc && http_auth_match_rules(require, username->ptr, NULL, NULL) 732 ? HANDLER_GO_ON 733 : HANDLER_ERROR; 734 } 735 736 737 int mod_authn_file_plugin_init(plugin *p); 738 int mod_authn_file_plugin_init(plugin *p) { 739 p->version = LIGHTTPD_VERSION_ID; 740 p->name = "authn_file"; 741 p->init = mod_authn_file_init; 742 p->set_defaults= mod_authn_file_set_defaults; 743 744 return 0; 745 } 746