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