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
INIT_FUNC(mod_authn_file_init)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 = ck_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
mod_authn_file_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)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
mod_authn_file_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)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
mod_authn_file_patch_config(request_st * const r,plugin_data * const p)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
SETDEFAULTS_FUNC(mod_authn_file_set_defaults)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
mod_authn_file_digest(http_auth_info_t * ai,const char * pw,size_t pwlen)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
mod_authn_file_htdigest_get_loop(const char * data,const buffer * auth_fn,http_auth_info_t * ai,log_error_st * errh)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
mod_authn_file_htdigest_get(request_st * const r,void * p_d,http_auth_info_t * const ai)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
mod_authn_file_htdigest_digest(request_st * const r,void * p_d,http_auth_info_t * const ai)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
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)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
mod_authn_file_htpasswd_get(const buffer * auth_fn,const char * username,size_t userlen,buffer * password,log_error_st * errh)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
mod_authn_file_plain_digest(request_st * const r,void * p_d,http_auth_info_t * const ai)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
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)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
to64(char * s,unsigned long v,int n)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
apr_md5_encode(const char * pw,const char * salt,char * result,size_t nbytes)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)
mod_authn_file_crypt_cmp(const buffer * const password,const char * const pw)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
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)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 __attribute_cold__
741 int mod_authn_file_plugin_init(plugin *p);
mod_authn_file_plugin_init(plugin * p)742 int mod_authn_file_plugin_init(plugin *p) {
743 p->version = LIGHTTPD_VERSION_ID;
744 p->name = "authn_file";
745 p->init = mod_authn_file_init;
746 p->set_defaults= mod_authn_file_set_defaults;
747
748 return 0;
749 }
750