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