xref: /lighttpd1.4/src/mod_authn_file.c (revision 2e0edb85)
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