Skip to content

Commit 3a152b3

Browse files
committed
added $apr1$ support for htpasswd (fixes #870)
git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@1369 152afb58-edef-0310-8abb-c4023f1b3aa9
1 parent 68033b8 commit 3a152b3

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

src/http_auth.c

+196
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@
3737
# include "md5.h"
3838
#endif
3939

40+
/**
41+
* the $apr1$ handling is taken from apache 1.3.x
42+
*/
43+
44+
/*
45+
* The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
46+
* MD5 crypt() function, which is licenced as follows:
47+
* ----------------------------------------------------------------------------
48+
* "THE BEER-WARE LICENSE" (Revision 42):
49+
* <[email protected]> wrote this file. As long as you retain this notice you
50+
* can do whatever you want with this stuff. If we meet some day, and you think
51+
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
52+
* ----------------------------------------------------------------------------
53+
*/
54+
4055
handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
4156

4257
static const char base64_pad = '=';
@@ -403,6 +418,178 @@ static int http_auth_match_rules(server *srv, mod_auth_plugin_data *p, const cha
403418
return -1;
404419
}
405420

421+
#define APR_MD5_DIGESTSIZE 16
422+
#define APR1_ID "$apr1$"
423+
424+
/*
425+
* The following MD5 password encryption code was largely borrowed from
426+
* the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is
427+
* licenced as stated at the top of this file.
428+
*/
429+
430+
static void to64(char *s, unsigned long v, int n)
431+
{
432+
static unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */
433+
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
434+
435+
while (--n >= 0) {
436+
*s++ = itoa64[v&0x3f];
437+
v >>= 6;
438+
}
439+
}
440+
441+
static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
442+
/*
443+
* Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
444+
* plus 4 for the '$' separators, plus the password hash itself.
445+
* Let's leave a goodly amount of leeway.
446+
*/
447+
448+
char passwd[120], *p;
449+
const char *sp, *ep;
450+
unsigned char final[APR_MD5_DIGESTSIZE];
451+
ssize_t sl, pl, i;
452+
MD5_CTX ctx, ctx1;
453+
unsigned long l;
454+
455+
/*
456+
* Refine the salt first. It's possible we were given an already-hashed
457+
* string as the salt argument, so extract the actual salt value from it
458+
* if so. Otherwise just use the string up to the first '$' as the salt.
459+
*/
460+
sp = salt;
461+
462+
/*
463+
* If it starts with the magic string, then skip that.
464+
*/
465+
if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) {
466+
sp += strlen(APR1_ID);
467+
}
468+
469+
/*
470+
* It stops at the first '$' or 8 chars, whichever comes first
471+
*/
472+
for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
473+
continue;
474+
}
475+
476+
/*
477+
* Get the length of the true salt
478+
*/
479+
sl = ep - sp;
480+
481+
/*
482+
* 'Time to make the doughnuts..'
483+
*/
484+
MD5_Init(&ctx);
485+
486+
/*
487+
* The password first, since that is what is most unknown
488+
*/
489+
MD5_Update(&ctx, pw, strlen(pw));
490+
491+
/*
492+
* Then our magic string
493+
*/
494+
MD5_Update(&ctx, APR1_ID, strlen(APR1_ID));
495+
496+
/*
497+
* Then the raw salt
498+
*/
499+
MD5_Update(&ctx, sp, sl);
500+
501+
/*
502+
* Then just as many characters of the MD5(pw, salt, pw)
503+
*/
504+
MD5_Init(&ctx1);
505+
MD5_Update(&ctx1, pw, strlen(pw));
506+
MD5_Update(&ctx1, sp, sl);
507+
MD5_Update(&ctx1, pw, strlen(pw));
508+
MD5_Final(final, &ctx1);
509+
for (pl = strlen(pw); pl > 0; pl -= APR_MD5_DIGESTSIZE) {
510+
MD5_Update(&ctx, final,
511+
(pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl);
512+
}
513+
514+
/*
515+
* Don't leave anything around in vm they could use.
516+
*/
517+
memset(final, 0, sizeof(final));
518+
519+
/*
520+
* Then something really weird...
521+
*/
522+
for (i = strlen(pw); i != 0; i >>= 1) {
523+
if (i & 1) {
524+
MD5_Update(&ctx, final, 1);
525+
}
526+
else {
527+
MD5_Update(&ctx, pw, 1);
528+
}
529+
}
530+
531+
/*
532+
* Now make the output string. We know our limitations, so we
533+
* can use the string routines without bounds checking.
534+
*/
535+
strcpy(passwd, APR1_ID);
536+
strncat(passwd, sp, sl);
537+
strcat(passwd, "$");
538+
539+
MD5_Final(final, &ctx);
540+
541+
/*
542+
* And now, just to make sure things don't run too fast..
543+
* On a 60 Mhz Pentium this takes 34 msec, so you would
544+
* need 30 seconds to build a 1000 entry dictionary...
545+
*/
546+
for (i = 0; i < 1000; i++) {
547+
MD5_Init(&ctx1);
548+
if (i & 1) {
549+
MD5_Update(&ctx1, pw, strlen(pw));
550+
}
551+
else {
552+
MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
553+
}
554+
if (i % 3) {
555+
MD5_Update(&ctx1, sp, sl);
556+
}
557+
558+
if (i % 7) {
559+
MD5_Update(&ctx1, pw, strlen(pw));
560+
}
561+
562+
if (i & 1) {
563+
MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
564+
}
565+
else {
566+
MD5_Update(&ctx1, pw, strlen(pw));
567+
}
568+
MD5_Final(final,&ctx1);
569+
}
570+
571+
p = passwd + strlen(passwd);
572+
573+
l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4;
574+
l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4;
575+
l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4;
576+
l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4;
577+
l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4;
578+
l = final[11] ; to64(p, l, 2); p += 2;
579+
*p = '\0';
580+
581+
/*
582+
* Don't leave anything around in vm they could use.
583+
*/
584+
memset(final, 0, sizeof(final));
585+
586+
/* FIXME
587+
*/
588+
#define apr_cpystrn strncpy
589+
apr_cpystrn(result, passwd, nbytes - 1);
590+
}
591+
592+
406593
/**
407594
*
408595
*
@@ -439,6 +626,14 @@ static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p
439626
return 0;
440627
}
441628
} else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
629+
char sample[120];
630+
if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) {
631+
/*
632+
* The hash was created using $apr1$ custom algorithm.
633+
*/
634+
apr_md5_encode(pw, password->ptr, sample, sizeof(sample));
635+
return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
636+
} else {
442637
#ifdef HAVE_CRYPT
443638
char salt[32];
444639
char *crypted;
@@ -494,6 +689,7 @@ static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p
494689
}
495690

496691
#endif
692+
}
497693
} else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
498694
if (0 == strcmp(password->ptr, pw)) {
499695
return 0;

0 commit comments

Comments
 (0)