6 #include "inet_ntop_cache.h"
12 #elif defined(__linux__)
13 /* linux needs _XOPEN_SOURCE */
14 # define _XOPEN_SOURCE
17 #if defined(HAVE_LIBCRYPT) && !defined(HAVE_CRYPT)
18 /* always assume crypt() is present if we have -lcrypt */
22 #include <sys/types.h>
37 #include <openssl/sha.h>
40 #include "safe_memclear.h"
44 typedef unsigned char HASH
[HASHLEN
];
45 typedef char HASHHEX
[HASHHEXLEN
+1];
47 static void CvtHex(const HASH Bin
, char (*Hex
)[33]) {
48 li_tohex(*Hex
, sizeof(*Hex
), (const char*) Bin
, 16);
53 * the $apr1$ handling is taken from apache 1.3.x
57 * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
58 * MD5 crypt() function, which is licenced as follows:
59 * ----------------------------------------------------------------------------
60 * "THE BEER-WARE LICENSE" (Revision 42):
61 * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
62 * can do whatever you want with this stuff. If we meet some day, and you think
63 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
64 * ----------------------------------------------------------------------------
67 handler_t
auth_ldap_init(server
*srv
, mod_auth_plugin_config
*s
);
69 static int http_auth_get_password(server
*srv
, mod_auth_plugin_data
*p
, buffer
*username
, buffer
*realm
, buffer
*password
) {
70 if (buffer_is_empty(username
) || buffer_is_empty(realm
)) return -1;
72 if (p
->conf
.auth_backend
== AUTH_BACKEND_HTDIGEST
) {
76 if (buffer_string_is_empty(p
->conf
.auth_htdigest_userfile
)) return -1;
78 fp
= fopen(p
->conf
.auth_htdigest_userfile
->ptr
, "r");
80 log_error_write(srv
, __FILE__
, __LINE__
, "sbss", "opening digest-userfile", p
->conf
.auth_htdigest_userfile
, "failed:", strerror(errno
));
85 while (NULL
!= fgets(f_user
, sizeof(f_user
), fp
)) {
86 char *f_pwd
, *f_realm
;
89 /* skip blank lines and comment lines (beginning '#') */
90 if (f_user
[0] == '#' || f_user
[0] == '\n' || f_user
[0] == '\0') continue;
95 * user:realm:md5(user:realm:password)
98 if (NULL
== (f_realm
= strchr(f_user
, ':'))) {
99 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
100 "parsed error in", p
->conf
.auth_htdigest_userfile
,
101 "expected 'username:realm:hashed password'");
103 continue; /* skip bad lines */
106 if (NULL
== (f_pwd
= strchr(f_realm
+ 1, ':'))) {
107 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
108 "parsed error in", p
->conf
.auth_plain_userfile
,
109 "expected 'username:realm:hashed password'");
111 continue; /* skip bad lines */
114 /* get pointers to the fields */
115 u_len
= f_realm
- f_user
;
117 r_len
= f_pwd
- f_realm
;
120 if (buffer_string_length(username
) == u_len
&&
121 (buffer_string_length(realm
) == r_len
) &&
122 (0 == strncmp(username
->ptr
, f_user
, u_len
)) &&
123 (0 == strncmp(realm
->ptr
, f_realm
, r_len
))) {
126 size_t pwd_len
= strlen(f_pwd
);
127 if (f_pwd
[pwd_len
-1] == '\n') --pwd_len
;
129 buffer_copy_string_len(password
, f_pwd
, pwd_len
);
137 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_HTPASSWD
||
138 p
->conf
.auth_backend
== AUTH_BACKEND_PLAIN
) {
143 auth_fn
= (p
->conf
.auth_backend
== AUTH_BACKEND_HTPASSWD
) ? p
->conf
.auth_htpasswd_userfile
: p
->conf
.auth_plain_userfile
;
145 if (buffer_string_is_empty(auth_fn
)) return -1;
147 fp
= fopen(auth_fn
->ptr
, "r");
149 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
150 "opening plain-userfile", auth_fn
, "failed:", strerror(errno
));
155 while (NULL
!= fgets(f_user
, sizeof(f_user
), fp
)) {
159 /* skip blank lines and comment lines (beginning '#') */
160 if (f_user
[0] == '#' || f_user
[0] == '\n' || f_user
[0] == '\0') continue;
165 * user:crypted passwd
168 if (NULL
== (f_pwd
= strchr(f_user
, ':'))) {
169 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
170 "parsed error in", auth_fn
,
171 "expected 'username:hashed password'");
173 continue; /* skip bad lines */
176 /* get pointers to the fields */
177 u_len
= f_pwd
- f_user
;
180 if (buffer_string_length(username
) == u_len
&&
181 (0 == strncmp(username
->ptr
, f_user
, u_len
))) {
184 size_t pwd_len
= strlen(f_pwd
);
185 if (f_pwd
[pwd_len
-1] == '\n') --pwd_len
;
187 buffer_copy_string_len(password
, f_pwd
, pwd_len
);
195 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_LDAP
) {
202 int http_auth_match_rules(server
*srv
, array
*req
, const char *username
, const char *group
, const char *host
) {
203 const char *r
= NULL
, *rules
= NULL
;
205 data_string
*require
;
210 require
= (data_string
*)array_get_element(req
, "require");
211 if (!require
) return -1; /*(should not happen; config is validated at startup)*/
213 /* if we get here, the user we got a authed user */
214 if (0 == strcmp(require
->value
->ptr
, "valid-user")) {
218 /* user=name1|group=name3|host=name4 */
220 /* seperate the string by | */
222 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "rules", require
->value
);
225 username_len
= username
? strlen(username
) : 0;
227 r
= rules
= require
->value
->ptr
;
231 const char *k
, *v
, *e
;
232 int k_len
, v_len
, r_len
;
239 r_len
= strlen(rules
) - (r
- rules
);
242 /* from r to r + r_len is a rule */
244 if (0 == strncmp(r
, "valid-user", r_len
)) {
245 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
246 "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
251 /* search for = in the rules */
252 if (NULL
== (eq
= strchr(r
, '='))) {
253 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
254 "parsing the 'require' section in 'auth.require' failed: a = is missing",
260 if (eq
> r
+ r_len
) {
261 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
262 "parsing the 'require' section in 'auth.require' failed: = out of range",
268 /* the part before the = is user|group|host */
273 v_len
= r_len
- k_len
- 1;
276 if (0 == strncmp(k
, "user", k_len
)) {
278 username_len
== v_len
&&
279 0 == strncmp(username
, v
, v_len
)) {
282 } else if (0 == strncmp(k
, "host", k_len
)) {
283 log_error_write(srv
, __FILE__
, __LINE__
, "s", "host ... (not implemented)");
285 log_error_write(srv
, __FILE__
, __LINE__
, "s", "unknown key");
288 } else if (k_len
== 5) {
289 if (0 == strncmp(k
, "group", k_len
)) {
290 log_error_write(srv
, __FILE__
, __LINE__
, "s", "group ... (not implemented)");
292 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "unknown key", k
);
296 log_error_write(srv
, __FILE__
, __LINE__
, "s", "unknown key");
304 log_error_write(srv
, __FILE__
, __LINE__
, "s", "nothing matched");
309 #define APR_MD5_DIGESTSIZE 16
310 #define APR1_ID "$apr1$"
313 * The following MD5 password encryption code was largely borrowed from
314 * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is
315 * licenced as stated at the top of this file.
318 static void to64(char *s
, unsigned long v
, int n
)
320 static const unsigned char itoa64
[] = /* 0 ... 63 => ASCII - 64 */
321 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
324 *s
++ = itoa64
[v
&0x3f];
329 static void apr_md5_encode(const char *pw
, const char *salt
, char *result
, size_t nbytes
) {
331 * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
332 * plus 4 for the '$' separators, plus the password hash itself.
333 * Let's leave a goodly amount of leeway.
336 char passwd
[120], *p
;
338 unsigned char final
[APR_MD5_DIGESTSIZE
];
340 li_MD5_CTX ctx
, ctx1
;
344 * Refine the salt first. It's possible we were given an already-hashed
345 * string as the salt argument, so extract the actual salt value from it
346 * if so. Otherwise just use the string up to the first '$' as the salt.
351 * If it starts with the magic string, then skip that.
353 if (!strncmp(sp
, APR1_ID
, strlen(APR1_ID
))) {
354 sp
+= strlen(APR1_ID
);
358 * It stops at the first '$' or 8 chars, whichever comes first
360 for (ep
= sp
; (*ep
!= '\0') && (*ep
!= '$') && (ep
< (sp
+ 8)); ep
++) {
365 * Get the length of the true salt
370 * 'Time to make the doughnuts..'
375 * The password first, since that is what is most unknown
377 li_MD5_Update(&ctx
, pw
, strlen(pw
));
380 * Then our magic string
382 li_MD5_Update(&ctx
, APR1_ID
, strlen(APR1_ID
));
387 li_MD5_Update(&ctx
, sp
, sl
);
390 * Then just as many characters of the MD5(pw, salt, pw)
393 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
394 li_MD5_Update(&ctx1
, sp
, sl
);
395 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
396 li_MD5_Final(final
, &ctx1
);
397 for (pl
= strlen(pw
); pl
> 0; pl
-= APR_MD5_DIGESTSIZE
) {
400 (pl
> APR_MD5_DIGESTSIZE
) ? APR_MD5_DIGESTSIZE
: pl
);
404 * Don't leave anything around in vm they could use.
406 memset(final
, 0, sizeof(final
));
409 * Then something really weird...
411 for (i
= strlen(pw
); i
!= 0; i
>>= 1) {
413 li_MD5_Update(&ctx
, final
, 1);
416 li_MD5_Update(&ctx
, pw
, 1);
421 * Now make the output string. We know our limitations, so we
422 * can use the string routines without bounds checking.
424 strcpy(passwd
, APR1_ID
);
425 strncat(passwd
, sp
, sl
);
428 li_MD5_Final(final
, &ctx
);
431 * And now, just to make sure things don't run too fast..
432 * On a 60 Mhz Pentium this takes 34 msec, so you would
433 * need 30 seconds to build a 1000 entry dictionary...
435 for (i
= 0; i
< 1000; i
++) {
438 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
441 li_MD5_Update(&ctx1
, final
, APR_MD5_DIGESTSIZE
);
444 li_MD5_Update(&ctx1
, sp
, sl
);
448 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
452 li_MD5_Update(&ctx1
, final
, APR_MD5_DIGESTSIZE
);
455 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
457 li_MD5_Final(final
,&ctx1
);
460 p
= passwd
+ strlen(passwd
);
462 l
= (final
[ 0]<<16) | (final
[ 6]<<8) | final
[12]; to64(p
, l
, 4); p
+= 4;
463 l
= (final
[ 1]<<16) | (final
[ 7]<<8) | final
[13]; to64(p
, l
, 4); p
+= 4;
464 l
= (final
[ 2]<<16) | (final
[ 8]<<8) | final
[14]; to64(p
, l
, 4); p
+= 4;
465 l
= (final
[ 3]<<16) | (final
[ 9]<<8) | final
[15]; to64(p
, l
, 4); p
+= 4;
466 l
= (final
[ 4]<<16) | (final
[10]<<8) | final
[ 5]; to64(p
, l
, 4); p
+= 4;
467 l
= final
[11] ; to64(p
, l
, 2); p
+= 2;
471 * Don't leave anything around in vm they could use.
473 safe_memclear(final
, sizeof(final
));
477 #define apr_cpystrn strncpy
478 apr_cpystrn(result
, passwd
, nbytes
- 1);
482 static void apr_sha_encode(const char *pw
, char *result
, size_t nbytes
) {
483 unsigned char digest
[20];
484 size_t base64_written
;
486 SHA1((const unsigned char*) pw
, strlen(pw
), digest
);
488 memset(result
, 0, nbytes
);
490 /* need 5 bytes for "{SHA}", 28 for base64 (3 bytes -> 4 bytes) of SHA1 (20 bytes), 1 terminating */
491 if (nbytes
< 5 + 28 + 1) return;
493 memcpy(result
, "{SHA}", 5);
494 base64_written
= li_to_base64(result
+ 5, nbytes
- 5, digest
, 20, BASE64_STANDARD
);
495 force_assert(base64_written
== 28);
496 result
[5 + base64_written
] = '\0'; /* terminate string */
503 * @param password password-string from the auth-backend
504 * @param pw password-string from the client
507 static int http_auth_basic_password_compare(server
*srv
, mod_auth_plugin_data
*p
, array
*req
, buffer
*username
, buffer
*realm
, buffer
*password
, const char *pw
) {
511 if (p
->conf
.auth_backend
== AUTH_BACKEND_HTDIGEST
) {
515 * user:realm:md5(user:realm:password)
522 li_MD5_Init(&Md5Ctx
);
523 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(username
));
524 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
525 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(realm
));
526 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
527 li_MD5_Update(&Md5Ctx
, (unsigned char *)pw
, strlen(pw
));
528 li_MD5_Final(HA1
, &Md5Ctx
);
532 if (0 == strcmp(password
->ptr
, a1
)) {
535 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_HTPASSWD
) {
537 if (!strncmp(password
->ptr
, APR1_ID
, strlen(APR1_ID
))) {
539 * The hash was created using $apr1$ custom algorithm.
541 apr_md5_encode(pw
, password
->ptr
, sample
, sizeof(sample
));
542 return (strcmp(sample
, password
->ptr
) == 0) ? 0 : 1;
544 } else if (0 == strncmp(password
->ptr
, "{SHA}", 5)) {
545 apr_sha_encode(pw
, sample
, sizeof(sample
));
546 return (strcmp(sample
, password
->ptr
) == 0) ? 0 : 1;
549 #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
551 #if defined(HAVE_CRYPT_R)
552 struct crypt_data crypt_tmp_data
;
553 crypt_tmp_data
.initialized
= 0;
556 /* a simple DES password is 2 + 11 characters. everything else should be longer. */
557 if (buffer_string_length(password
) < 13) {
561 #if defined(HAVE_CRYPT_R)
562 if (0 == (crypted
= crypt_r(pw
, password
->ptr
, &crypt_tmp_data
))) {
564 if (0 == (crypted
= crypt(pw
, password
->ptr
))) {
570 if (0 == strcmp(password
->ptr
, crypted
)) {
575 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_PLAIN
) {
576 if (0 == strcmp(password
->ptr
, pw
)) {
579 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_LDAP
) {
582 LDAPMessage
*lm
, *first
;
585 char *attrs
[] = { LDAP_NO_ATTRS
, NULL
};
588 /* for now we stay synchronous */
591 * 1. connect anonymously (done in plugin init)
592 * 2. get DN for uid = username
593 * 3. auth against ldap server
594 * 4. (optional) check a field
601 * we have to protect us againt username which modifies out filter in
605 len
= buffer_string_length(username
);
606 for (i
= 0; i
< len
; i
++) {
607 char c
= username
->ptr
[i
];
617 log_error_write(srv
, __FILE__
, __LINE__
, "sbd",
618 "ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username
, i
);
624 if (p
->conf
.auth_ldap_allow_empty_pw
!= 1 && pw
[0] == '\0')
628 buffer_copy_buffer(p
->ldap_filter
, p
->conf
.ldap_filter_pre
);
629 buffer_append_string_buffer(p
->ldap_filter
, username
);
630 buffer_append_string_buffer(p
->ldap_filter
, p
->conf
.ldap_filter_post
);
634 if (p
->anon_conf
->ldap
== NULL
||
635 LDAP_SUCCESS
!= (ret
= ldap_search_s(p
->anon_conf
->ldap
, p
->conf
.auth_ldap_basedn
->ptr
, LDAP_SCOPE_SUBTREE
, p
->ldap_filter
->ptr
, attrs
, 0, &lm
))) {
637 /* try again; the ldap library sometimes fails for the first call but reconnects */
638 if (p
->anon_conf
->ldap
== NULL
|| ret
!= LDAP_SERVER_DOWN
||
639 LDAP_SUCCESS
!= (ret
= ldap_search_s(p
->anon_conf
->ldap
, p
->conf
.auth_ldap_basedn
->ptr
, LDAP_SCOPE_SUBTREE
, p
->ldap_filter
->ptr
, attrs
, 0, &lm
))) {
641 if (auth_ldap_init(srv
, p
->anon_conf
) != HANDLER_GO_ON
)
644 if (NULL
== p
->anon_conf
->ldap
) return -1;
646 if (LDAP_SUCCESS
!= (ret
= ldap_search_s(p
->anon_conf
->ldap
, p
->conf
.auth_ldap_basedn
->ptr
, LDAP_SCOPE_SUBTREE
, p
->ldap_filter
->ptr
, attrs
, 0, &lm
))) {
647 log_error_write(srv
, __FILE__
, __LINE__
, "sssb",
648 "ldap:", ldap_err2string(ret
), "filter:", p
->ldap_filter
);
654 if (NULL
== (first
= ldap_first_entry(p
->anon_conf
->ldap
, lm
))) {
655 log_error_write(srv
, __FILE__
, __LINE__
, "s", "ldap ...");
662 if (NULL
== (dn
= ldap_get_dn(p
->anon_conf
->ldap
, first
))) {
663 log_error_write(srv
, __FILE__
, __LINE__
, "s", "ldap ...");
674 if (NULL
== (ldap
= ldap_init(p
->conf
.auth_ldap_hostname
->ptr
, LDAP_PORT
))) {
675 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap ...", strerror(errno
));
680 if (LDAP_OPT_SUCCESS
!= (ret
= ldap_set_option(ldap
, LDAP_OPT_PROTOCOL_VERSION
, &ret
))) {
681 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap:", ldap_err2string(ret
));
688 if (p
->conf
.auth_ldap_starttls
== 1) {
689 if (LDAP_OPT_SUCCESS
!= (ret
= ldap_start_tls_s(ldap
, NULL
, NULL
))) {
690 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap startTLS failed:", ldap_err2string(ret
));
699 if (LDAP_SUCCESS
!= (ret
= ldap_simple_bind_s(ldap
, dn
, pw
))) {
700 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap:", ldap_err2string(ret
));
710 /* everything worked, good, access granted */
718 int http_auth_basic_check(server
*srv
, connection
*con
, mod_auth_plugin_data
*p
, array
*req
, const char *realm_str
) {
719 buffer
*username
, *password
;
724 realm
= (data_string
*)array_get_element(req
, "realm");
725 if (!realm
) return 0; /*(should not happen; config is validated at startup)*/
727 username
= buffer_init();
729 if (!buffer_append_base64_decode(username
, realm_str
, strlen(realm_str
), BASE64_STANDARD
)) {
730 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "decodeing base64-string failed", username
);
732 buffer_free(username
);
736 /* r2 == user:password */
737 if (NULL
== (pw
= strchr(username
->ptr
, ':'))) {
738 log_error_write(srv
, __FILE__
, __LINE__
, "sb", ": is missing in", username
);
740 buffer_free(username
);
744 buffer_string_set_length(username
, pw
- username
->ptr
);
747 password
= buffer_init();
748 /* copy password to r1 */
749 if (http_auth_get_password(srv
, p
, username
, realm
->value
, password
)) {
750 buffer_free(username
);
751 buffer_free(password
);
753 if (AUTH_BACKEND_UNSET
== p
->conf
.auth_backend
) {
754 log_error_write(srv
, __FILE__
, __LINE__
, "s", "auth.backend is not set");
756 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
762 /* password doesn't match */
763 if (http_auth_basic_password_compare(srv
, p
, req
, username
, realm
->value
, password
, pw
)) {
764 log_error_write(srv
, __FILE__
, __LINE__
, "sbsBss", "password doesn't match for", con
->uri
.path
, "username:", username
, ", IP:", inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
766 buffer_free(username
);
767 buffer_free(password
);
772 /* value is our allow-rules */
773 if (http_auth_match_rules(srv
, req
, username
->ptr
, NULL
, NULL
)) {
774 buffer_free(username
);
775 buffer_free(password
);
777 log_error_write(srv
, __FILE__
, __LINE__
, "s", "rules didn't match");
782 /* remember the username */
783 buffer_copy_buffer(p
->auth_user
, username
);
785 buffer_free(username
);
786 buffer_free(password
);
797 /* return values: -1: error/bad request, 0: failed, 1: success */
798 int http_auth_digest_check(server
*srv
, connection
*con
, mod_auth_plugin_data
*p
, array
*req
, const char *realm_str
) {
802 char *username
= NULL
;
806 char *algorithm
= NULL
;
810 char *respons
= NULL
;
813 const char *m
= NULL
;
815 buffer
*password
, *b
, *username_buf
, *realm_buf
;
827 digest_kv dkv
[10] = {
842 dkv
[0].ptr
= &username
;
846 dkv
[4].ptr
= &algorithm
;
848 dkv
[6].ptr
= &cnonce
;
850 dkv
[8].ptr
= &respons
;
854 if (p
->conf
.auth_backend
!= AUTH_BACKEND_HTDIGEST
&&
855 p
->conf
.auth_backend
!= AUTH_BACKEND_PLAIN
) {
856 log_error_write(srv
, __FILE__
, __LINE__
, "s",
857 "digest: unsupported backend (only htdigest or plain)");
862 b
= buffer_init_string(realm_str
);
864 /* parse credentials from client */
865 for (c
= b
->ptr
; *c
; c
++) {
866 /* skip whitespaces */
867 while (*c
== ' ' || *c
== '\t') c
++;
870 for (i
= 0; dkv
[i
].key
; i
++) {
871 if ((0 == strncmp(c
, dkv
[i
].key
, dkv
[i
].key_len
))) {
872 if ((c
[dkv
[i
].key_len
] == '"') &&
873 (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
+ 1, '"')))) {
874 /* value with "..." */
875 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
+ 1;
879 } else if (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
, ','))) {
880 /* value without "...", terminated by ',' */
881 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
886 /* value without "...", terminated by EOL */
887 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
895 if (p
->conf
.auth_debug
> 1) {
896 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "username", username
);
897 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "realm", realm
);
898 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "nonce", nonce
);
899 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "uri", uri
);
900 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "algorithm", algorithm
);
901 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "qop", qop
);
902 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "cnonce", cnonce
);
903 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "nc", nc
);
904 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "response", respons
);
907 /* check if everything is transmitted */
912 (qop
&& (!nc
|| !cnonce
)) ||
916 log_error_write(srv
, __FILE__
, __LINE__
, "s",
917 "digest: missing field");
924 * protect the md5-sess against missing cnonce and nonce
927 0 == strcasecmp(algorithm
, "md5-sess") &&
928 (!nonce
|| !cnonce
)) {
929 log_error_write(srv
, __FILE__
, __LINE__
, "s",
930 "digest: (md5-sess: missing field");
936 if (qop
&& strcasecmp(qop
, "auth-int") == 0) {
937 log_error_write(srv
, __FILE__
, __LINE__
, "s",
938 "digest: qop=auth-int not supported");
944 m
= get_http_method_name(con
->request
.http_method
);
947 /* password-string == HA1 */
948 password
= buffer_init();
949 username_buf
= buffer_init_string(username
);
950 realm_buf
= buffer_init_string(realm
);
951 if (http_auth_get_password(srv
, p
, username_buf
, realm_buf
, password
)) {
952 buffer_free(password
);
954 buffer_free(username_buf
);
955 buffer_free(realm_buf
);
959 buffer_free(username_buf
);
960 buffer_free(realm_buf
);
962 if (p
->conf
.auth_backend
== AUTH_BACKEND_PLAIN
) {
963 /* generate password from plain-text */
964 li_MD5_Init(&Md5Ctx
);
965 li_MD5_Update(&Md5Ctx
, (unsigned char *)username
, strlen(username
));
966 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
967 li_MD5_Update(&Md5Ctx
, (unsigned char *)realm
, strlen(realm
));
968 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
969 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(password
));
970 li_MD5_Final(HA1
, &Md5Ctx
);
971 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_HTDIGEST
) {
973 /* transform the 32-byte-hex-md5 to a 16-byte-md5 */
974 for (i
= 0; i
< HASHLEN
; i
++) {
975 HA1
[i
] = hex2int(password
->ptr
[i
*2]) << 4;
976 HA1
[i
] |= hex2int(password
->ptr
[i
*2+1]);
979 /* we already check that above */
983 buffer_free(password
);
985 /* detect if attacker is attempting to reuse valid digest for one uri
986 * on a different request uri. Might also happen if intermediate proxy
987 * altered client request line. (Altered request would not result in
988 * the same digest as that calculated by the client.) */
990 const size_t ulen
= strlen(uri
);
991 const size_t rlen
= buffer_string_length(con
->request
.uri
);
992 if (!buffer_is_equal_string(con
->request
.uri
, uri
, ulen
)
993 && !(rlen
< ulen
&& 0 == memcmp(con
->request
.uri
->ptr
, uri
, rlen
) && uri
[rlen
] == '?')) {
994 log_error_write(srv
, __FILE__
, __LINE__
, "sbssss",
995 "digest: auth failed: uri mismatch (", con
->request
.uri
, "!=", uri
, "), IP:", inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
1002 strcasecmp(algorithm
, "md5-sess") == 0) {
1003 li_MD5_Init(&Md5Ctx
);
1004 /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
1006 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
1007 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1008 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
1009 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1010 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
1011 li_MD5_Final(HA1
, &Md5Ctx
);
1016 /* calculate H(A2) */
1017 li_MD5_Init(&Md5Ctx
);
1018 li_MD5_Update(&Md5Ctx
, (unsigned char *)m
, strlen(m
));
1019 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1020 li_MD5_Update(&Md5Ctx
, (unsigned char *)uri
, strlen(uri
));
1021 /* qop=auth-int not supported, already checked above */
1023 if (qop && strcasecmp(qop, "auth-int") == 0) {
1024 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1025 li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
1028 li_MD5_Final(HA2
, &Md5Ctx
);
1029 CvtHex(HA2
, &HA2Hex
);
1031 /* calculate response */
1032 li_MD5_Init(&Md5Ctx
);
1033 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
1034 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1035 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
1036 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1038 li_MD5_Update(&Md5Ctx
, (unsigned char *)nc
, strlen(nc
));
1039 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1040 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
1041 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1042 li_MD5_Update(&Md5Ctx
, (unsigned char *)qop
, strlen(qop
));
1043 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1045 li_MD5_Update(&Md5Ctx
, (unsigned char *)HA2Hex
, HASHHEXLEN
);
1046 li_MD5_Final(RespHash
, &Md5Ctx
);
1047 CvtHex(RespHash
, &a2
);
1049 if (0 != strcmp(a2
, respons
)) {
1052 if (p
->conf
.auth_debug
) {
1053 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
1054 "digest: digest mismatch", a2
, respons
);
1057 log_error_write(srv
, __FILE__
, __LINE__
, "ssss",
1058 "digest: auth failed for ", username
, ": wrong password, IP:", inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
1064 /* value is our allow-rules */
1065 if (http_auth_match_rules(srv
, req
, username
, NULL
, NULL
)) {
1068 log_error_write(srv
, __FILE__
, __LINE__
, "s",
1069 "digest: rules did match");
1074 /* check age of nonce. Note that rand() is used in nonce generation
1075 * in http_auth_digest_generate_nonce(). If that were replaced
1076 * with nanosecond time, then nonce secret would remain unique enough
1077 * for the purposes of Digest auth, and would be reproducible (and
1078 * verifiable) if nanoseconds were inclued with seconds as part of the
1079 * nonce "timestamp:secret". Since that is not done, timestamp in
1080 * nonce could theoretically be modified and still produce same md5sum,
1081 * but that is highly unlikely within a 10 min (moving) window of valid
1082 * time relative to current time (now) */
1085 const unsigned char * const nonce_uns
= (unsigned char *)nonce
;
1086 for (i
= 0; i
< 8 && light_isxdigit(nonce_uns
[i
]); ++i
) {
1087 ts
= (ts
<< 4) + hex2int(nonce_uns
[i
]);
1089 if (i
!= 8 || nonce
[8] != ':'
1090 || ts
> srv
->cur_ts
|| srv
->cur_ts
- ts
> 600) { /*(10 mins)*/
1092 return -2; /* nonce is stale; have client regenerate digest */
1093 } /*(future: might send nextnonce when expiration is imminent)*/
1096 /* remember the username */
1097 buffer_copy_string(p
->auth_user
, username
);
1101 if (p
->conf
.auth_debug
) {
1102 log_error_write(srv
, __FILE__
, __LINE__
, "s",
1109 int http_auth_digest_generate_nonce(server
*srv
, mod_auth_plugin_data
*p
, buffer
*fn
, char (*out
)[33]) {
1112 char hh
[LI_ITOSTRING_LENGTH
];
1116 /* generate shared-secret */
1117 li_MD5_Init(&Md5Ctx
);
1118 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(fn
));
1119 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN("+"));
1121 /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1122 li_itostrn(hh
, sizeof(hh
), srv
->cur_ts
);
1123 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
1124 li_MD5_Update(&Md5Ctx
, (unsigned char *)srv
->entropy
, sizeof(srv
->entropy
));
1125 li_itostrn(hh
, sizeof(hh
), rand());
1126 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
1128 li_MD5_Final(h
, &Md5Ctx
);