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