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
) {
72 if (buffer_is_empty(username
) || buffer_is_empty(realm
)) return -1;
74 if (p
->conf
.auth_backend
== AUTH_BACKEND_HTDIGEST
) {
78 if (buffer_string_is_empty(p
->conf
.auth_htdigest_userfile
)) return -1;
80 if (0 != stream_open(&f
, p
->conf
.auth_htdigest_userfile
)) {
81 log_error_write(srv
, __FILE__
, __LINE__
, "sbss", "opening digest-userfile", p
->conf
.auth_htdigest_userfile
, "failed:", strerror(errno
));
88 while (f_line
- f
.start
!= f
.size
) {
89 char *f_user
, *f_pwd
, *e
, *f_realm
;
90 size_t u_len
, pwd_len
, r_len
;
97 * user:realm:md5(user:realm:password)
100 if (NULL
== (f_realm
= memchr(f_user
, ':', f
.size
- (f_user
- f
.start
) ))) {
101 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
102 "parsed error in", p
->conf
.auth_htdigest_userfile
,
103 "expected 'username:realm:hashed password'");
110 if (NULL
== (f_pwd
= memchr(f_realm
+ 1, ':', f
.size
- (f_realm
+ 1 - f
.start
)))) {
111 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
112 "parsed error in", p
->conf
.auth_plain_userfile
,
113 "expected 'username:realm:hashed password'");
120 /* get pointers to the fields */
121 u_len
= f_realm
- f_user
;
123 r_len
= f_pwd
- f_realm
;
126 if (NULL
!= (e
= memchr(f_pwd
, '\n', f
.size
- (f_pwd
- f
.start
)))) {
129 pwd_len
= f
.size
- (f_pwd
- f
.start
);
132 if (buffer_string_length(username
) == u_len
&&
133 (buffer_string_length(realm
) == r_len
) &&
134 (0 == strncmp(username
->ptr
, f_user
, u_len
)) &&
135 (0 == strncmp(realm
->ptr
, f_realm
, r_len
))) {
138 buffer_copy_string_len(password
, f_pwd
, pwd_len
);
151 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_HTPASSWD
||
152 p
->conf
.auth_backend
== AUTH_BACKEND_PLAIN
) {
157 auth_fn
= (p
->conf
.auth_backend
== AUTH_BACKEND_HTPASSWD
) ? p
->conf
.auth_htpasswd_userfile
: p
->conf
.auth_plain_userfile
;
159 if (buffer_string_is_empty(auth_fn
)) return -1;
161 if (0 != stream_open(&f
, auth_fn
)) {
162 log_error_write(srv
, __FILE__
, __LINE__
, "sbss",
163 "opening plain-userfile", auth_fn
, "failed:", strerror(errno
));
170 while (f_line
- f
.start
!= f
.size
) {
171 char *f_user
, *f_pwd
, *e
;
172 size_t u_len
, pwd_len
;
179 * user:crypted passwd
182 if (NULL
== (f_pwd
= memchr(f_user
, ':', f
.size
- (f_user
- f
.start
) ))) {
183 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
184 "parsed error in", auth_fn
,
185 "expected 'username:hashed password'");
192 /* get pointers to the fields */
193 u_len
= f_pwd
- f_user
;
196 if (NULL
!= (e
= memchr(f_pwd
, '\n', f
.size
- (f_pwd
- f
.start
)))) {
199 pwd_len
= f
.size
- (f_pwd
- f
.start
);
202 if (buffer_string_length(username
) == u_len
&&
203 (0 == strncmp(username
->ptr
, f_user
, u_len
))) {
206 buffer_copy_string_len(password
, f_pwd
, pwd_len
);
219 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_LDAP
) {
228 int http_auth_match_rules(server
*srv
, array
*req
, const char *username
, const char *group
, const char *host
) {
229 const char *r
= NULL
, *rules
= NULL
;
231 data_string
*require
;
236 require
= (data_string
*)array_get_element(req
, "require");
238 /* if we get here, the user we got a authed user */
239 if (0 == strcmp(require
->value
->ptr
, "valid-user")) {
243 /* user=name1|group=name3|host=name4 */
245 /* seperate the string by | */
247 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "rules", require
->value
);
250 username_len
= username
? strlen(username
) : 0;
252 r
= rules
= require
->value
->ptr
;
256 const char *k
, *v
, *e
;
257 int k_len
, v_len
, r_len
;
264 r_len
= strlen(rules
) - (r
- rules
);
267 /* from r to r + r_len is a rule */
269 if (0 == strncmp(r
, "valid-user", r_len
)) {
270 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
271 "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
276 /* search for = in the rules */
277 if (NULL
== (eq
= strchr(r
, '='))) {
278 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
279 "parsing the 'require' section in 'auth.require' failed: a = is missing",
285 if (eq
> r
+ r_len
) {
286 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
287 "parsing the 'require' section in 'auth.require' failed: = out of range",
293 /* the part before the = is user|group|host */
298 v_len
= r_len
- k_len
- 1;
301 if (0 == strncmp(k
, "user", k_len
)) {
303 username_len
== v_len
&&
304 0 == strncmp(username
, v
, v_len
)) {
307 } else if (0 == strncmp(k
, "host", k_len
)) {
308 log_error_write(srv
, __FILE__
, __LINE__
, "s", "host ... (not implemented)");
310 log_error_write(srv
, __FILE__
, __LINE__
, "s", "unknown key");
313 } else if (k_len
== 5) {
314 if (0 == strncmp(k
, "group", k_len
)) {
315 log_error_write(srv
, __FILE__
, __LINE__
, "s", "group ... (not implemented)");
317 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "unknown key", k
);
321 log_error_write(srv
, __FILE__
, __LINE__
, "s", "unknown key");
329 log_error_write(srv
, __FILE__
, __LINE__
, "s", "nothing matched");
334 #define APR_MD5_DIGESTSIZE 16
335 #define APR1_ID "$apr1$"
338 * The following MD5 password encryption code was largely borrowed from
339 * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is
340 * licenced as stated at the top of this file.
343 static void to64(char *s
, unsigned long v
, int n
)
345 static const unsigned char itoa64
[] = /* 0 ... 63 => ASCII - 64 */
346 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
349 *s
++ = itoa64
[v
&0x3f];
354 static void apr_md5_encode(const char *pw
, const char *salt
, char *result
, size_t nbytes
) {
356 * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
357 * plus 4 for the '$' separators, plus the password hash itself.
358 * Let's leave a goodly amount of leeway.
361 char passwd
[120], *p
;
363 unsigned char final
[APR_MD5_DIGESTSIZE
];
365 li_MD5_CTX ctx
, ctx1
;
369 * Refine the salt first. It's possible we were given an already-hashed
370 * string as the salt argument, so extract the actual salt value from it
371 * if so. Otherwise just use the string up to the first '$' as the salt.
376 * If it starts with the magic string, then skip that.
378 if (!strncmp(sp
, APR1_ID
, strlen(APR1_ID
))) {
379 sp
+= strlen(APR1_ID
);
383 * It stops at the first '$' or 8 chars, whichever comes first
385 for (ep
= sp
; (*ep
!= '\0') && (*ep
!= '$') && (ep
< (sp
+ 8)); ep
++) {
390 * Get the length of the true salt
395 * 'Time to make the doughnuts..'
400 * The password first, since that is what is most unknown
402 li_MD5_Update(&ctx
, pw
, strlen(pw
));
405 * Then our magic string
407 li_MD5_Update(&ctx
, APR1_ID
, strlen(APR1_ID
));
412 li_MD5_Update(&ctx
, sp
, sl
);
415 * Then just as many characters of the MD5(pw, salt, pw)
418 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
419 li_MD5_Update(&ctx1
, sp
, sl
);
420 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
421 li_MD5_Final(final
, &ctx1
);
422 for (pl
= strlen(pw
); pl
> 0; pl
-= APR_MD5_DIGESTSIZE
) {
425 (pl
> APR_MD5_DIGESTSIZE
) ? APR_MD5_DIGESTSIZE
: pl
);
429 * Don't leave anything around in vm they could use.
431 memset(final
, 0, sizeof(final
));
434 * Then something really weird...
436 for (i
= strlen(pw
); i
!= 0; i
>>= 1) {
438 li_MD5_Update(&ctx
, final
, 1);
441 li_MD5_Update(&ctx
, pw
, 1);
446 * Now make the output string. We know our limitations, so we
447 * can use the string routines without bounds checking.
449 strcpy(passwd
, APR1_ID
);
450 strncat(passwd
, sp
, sl
);
453 li_MD5_Final(final
, &ctx
);
456 * And now, just to make sure things don't run too fast..
457 * On a 60 Mhz Pentium this takes 34 msec, so you would
458 * need 30 seconds to build a 1000 entry dictionary...
460 for (i
= 0; i
< 1000; i
++) {
463 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
466 li_MD5_Update(&ctx1
, final
, APR_MD5_DIGESTSIZE
);
469 li_MD5_Update(&ctx1
, sp
, sl
);
473 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
477 li_MD5_Update(&ctx1
, final
, APR_MD5_DIGESTSIZE
);
480 li_MD5_Update(&ctx1
, pw
, strlen(pw
));
482 li_MD5_Final(final
,&ctx1
);
485 p
= passwd
+ strlen(passwd
);
487 l
= (final
[ 0]<<16) | (final
[ 6]<<8) | final
[12]; to64(p
, l
, 4); p
+= 4;
488 l
= (final
[ 1]<<16) | (final
[ 7]<<8) | final
[13]; to64(p
, l
, 4); p
+= 4;
489 l
= (final
[ 2]<<16) | (final
[ 8]<<8) | final
[14]; to64(p
, l
, 4); p
+= 4;
490 l
= (final
[ 3]<<16) | (final
[ 9]<<8) | final
[15]; to64(p
, l
, 4); p
+= 4;
491 l
= (final
[ 4]<<16) | (final
[10]<<8) | final
[ 5]; to64(p
, l
, 4); p
+= 4;
492 l
= final
[11] ; to64(p
, l
, 2); p
+= 2;
496 * Don't leave anything around in vm they could use.
498 safe_memclear(final
, sizeof(final
));
502 #define apr_cpystrn strncpy
503 apr_cpystrn(result
, passwd
, nbytes
- 1);
507 static void apr_sha_encode(const char *pw
, char *result
, size_t nbytes
) {
508 unsigned char digest
[20];
509 size_t base64_written
;
511 SHA1((const unsigned char*) pw
, strlen(pw
), digest
);
513 memset(result
, 0, nbytes
);
515 /* need 5 bytes for "{SHA}", 28 for base64 (3 bytes -> 4 bytes) of SHA1 (20 bytes), 1 terminating */
516 if (nbytes
< 5 + 28 + 1) return;
518 memcpy(result
, "{SHA}", 5);
519 base64_written
= li_to_base64(result
+ 5, nbytes
- 5, digest
, 20, BASE64_STANDARD
);
520 force_assert(base64_written
== 28);
521 result
[5 + base64_written
] = '\0'; /* terminate string */
528 * @param password password-string from the auth-backend
529 * @param pw password-string from the client
532 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
) {
536 if (p
->conf
.auth_backend
== AUTH_BACKEND_HTDIGEST
) {
540 * user:realm:md5(user:realm:password)
547 li_MD5_Init(&Md5Ctx
);
548 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(username
));
549 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
550 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(realm
));
551 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
552 li_MD5_Update(&Md5Ctx
, (unsigned char *)pw
, strlen(pw
));
553 li_MD5_Final(HA1
, &Md5Ctx
);
557 if (0 == strcmp(password
->ptr
, a1
)) {
560 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_HTPASSWD
) {
562 if (!strncmp(password
->ptr
, APR1_ID
, strlen(APR1_ID
))) {
564 * The hash was created using $apr1$ custom algorithm.
566 apr_md5_encode(pw
, password
->ptr
, sample
, sizeof(sample
));
567 return (strcmp(sample
, password
->ptr
) == 0) ? 0 : 1;
569 } else if (0 == strncmp(password
->ptr
, "{SHA}", 5)) {
570 apr_sha_encode(pw
, sample
, sizeof(sample
));
571 return (strcmp(sample
, password
->ptr
) == 0) ? 0 : 1;
574 #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
576 #if defined(HAVE_CRYPT_R)
577 struct crypt_data crypt_tmp_data
;
578 crypt_tmp_data
.initialized
= 0;
581 /* a simple DES password is 2 + 11 characters. everything else should be longer. */
582 if (buffer_string_length(password
) < 13) {
586 #if defined(HAVE_CRYPT_R)
587 if (0 == (crypted
= crypt_r(pw
, password
->ptr
, &crypt_tmp_data
))) {
589 if (0 == (crypted
= crypt(pw
, password
->ptr
))) {
595 if (0 == strcmp(password
->ptr
, crypted
)) {
600 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_PLAIN
) {
601 if (0 == strcmp(password
->ptr
, pw
)) {
604 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_LDAP
) {
607 LDAPMessage
*lm
, *first
;
610 char *attrs
[] = { LDAP_NO_ATTRS
, NULL
};
613 /* for now we stay synchronous */
616 * 1. connect anonymously (done in plugin init)
617 * 2. get DN for uid = username
618 * 3. auth against ldap server
619 * 4. (optional) check a field
626 * we have to protect us againt username which modifies out filter in
630 len
= buffer_string_length(username
);
631 for (i
= 0; i
< len
; i
++) {
632 char c
= username
->ptr
[i
];
642 log_error_write(srv
, __FILE__
, __LINE__
, "sbd",
643 "ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username
, i
);
649 if (p
->conf
.auth_ldap_allow_empty_pw
!= 1 && pw
[0] == '\0')
653 buffer_copy_buffer(p
->ldap_filter
, p
->conf
.ldap_filter_pre
);
654 buffer_append_string_buffer(p
->ldap_filter
, username
);
655 buffer_append_string_buffer(p
->ldap_filter
, p
->conf
.ldap_filter_post
);
659 if (p
->anon_conf
->ldap
== NULL
||
660 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
))) {
662 /* try again; the ldap library sometimes fails for the first call but reconnects */
663 if (p
->anon_conf
->ldap
== NULL
|| ret
!= LDAP_SERVER_DOWN
||
664 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
))) {
666 if (auth_ldap_init(srv
, p
->anon_conf
) != HANDLER_GO_ON
)
669 if (NULL
== p
->anon_conf
->ldap
) return -1;
671 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
))) {
672 log_error_write(srv
, __FILE__
, __LINE__
, "sssb",
673 "ldap:", ldap_err2string(ret
), "filter:", p
->ldap_filter
);
679 if (NULL
== (first
= ldap_first_entry(p
->anon_conf
->ldap
, lm
))) {
680 log_error_write(srv
, __FILE__
, __LINE__
, "s", "ldap ...");
687 if (NULL
== (dn
= ldap_get_dn(p
->anon_conf
->ldap
, first
))) {
688 log_error_write(srv
, __FILE__
, __LINE__
, "s", "ldap ...");
699 if (NULL
== (ldap
= ldap_init(p
->conf
.auth_ldap_hostname
->ptr
, LDAP_PORT
))) {
700 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap ...", strerror(errno
));
705 if (LDAP_OPT_SUCCESS
!= (ret
= ldap_set_option(ldap
, LDAP_OPT_PROTOCOL_VERSION
, &ret
))) {
706 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap:", ldap_err2string(ret
));
713 if (p
->conf
.auth_ldap_starttls
== 1) {
714 if (LDAP_OPT_SUCCESS
!= (ret
= ldap_start_tls_s(ldap
, NULL
, NULL
))) {
715 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap startTLS failed:", ldap_err2string(ret
));
724 if (LDAP_SUCCESS
!= (ret
= ldap_simple_bind_s(ldap
, dn
, pw
))) {
725 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "ldap:", ldap_err2string(ret
));
735 /* everything worked, good, access granted */
743 int http_auth_basic_check(server
*srv
, connection
*con
, mod_auth_plugin_data
*p
, array
*req
, const char *realm_str
) {
744 buffer
*username
, *password
;
749 realm
= (data_string
*)array_get_element(req
, "realm");
751 username
= buffer_init();
753 if (!buffer_append_base64_decode(username
, realm_str
, strlen(realm_str
), BASE64_STANDARD
)) {
754 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "decodeing base64-string failed", username
);
756 buffer_free(username
);
760 /* r2 == user:password */
761 if (NULL
== (pw
= strchr(username
->ptr
, ':'))) {
762 log_error_write(srv
, __FILE__
, __LINE__
, "sb", ": is missing in", username
);
764 buffer_free(username
);
768 buffer_string_set_length(username
, pw
- username
->ptr
);
771 password
= buffer_init();
772 /* copy password to r1 */
773 if (http_auth_get_password(srv
, p
, username
, realm
->value
, password
)) {
774 buffer_free(username
);
775 buffer_free(password
);
777 if (AUTH_BACKEND_UNSET
== p
->conf
.auth_backend
) {
778 log_error_write(srv
, __FILE__
, __LINE__
, "s", "auth.backend is not set");
780 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
786 /* password doesn't match */
787 if (http_auth_basic_password_compare(srv
, p
, req
, username
, realm
->value
, password
, pw
)) {
788 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
)));
790 buffer_free(username
);
791 buffer_free(password
);
796 /* value is our allow-rules */
797 if (http_auth_match_rules(srv
, req
, username
->ptr
, NULL
, NULL
)) {
798 buffer_free(username
);
799 buffer_free(password
);
801 log_error_write(srv
, __FILE__
, __LINE__
, "s", "rules didn't match");
806 /* remember the username */
807 buffer_copy_buffer(p
->auth_user
, username
);
809 buffer_free(username
);
810 buffer_free(password
);
821 /* return values: -1: error/bad request, 0: failed, 1: success */
822 int http_auth_digest_check(server
*srv
, connection
*con
, mod_auth_plugin_data
*p
, array
*req
, const char *realm_str
) {
826 char *username
= NULL
;
830 char *algorithm
= NULL
;
834 char *respons
= NULL
;
837 const char *m
= NULL
;
839 buffer
*password
, *b
, *username_buf
, *realm_buf
;
851 digest_kv dkv
[10] = {
866 dkv
[0].ptr
= &username
;
870 dkv
[4].ptr
= &algorithm
;
872 dkv
[6].ptr
= &cnonce
;
874 dkv
[8].ptr
= &respons
;
878 if (p
->conf
.auth_backend
!= AUTH_BACKEND_HTDIGEST
&&
879 p
->conf
.auth_backend
!= AUTH_BACKEND_PLAIN
) {
880 log_error_write(srv
, __FILE__
, __LINE__
, "s",
881 "digest: unsupported backend (only htdigest or plain)");
886 b
= buffer_init_string(realm_str
);
888 /* parse credentials from client */
889 for (c
= b
->ptr
; *c
; c
++) {
890 /* skip whitespaces */
891 while (*c
== ' ' || *c
== '\t') c
++;
894 for (i
= 0; dkv
[i
].key
; i
++) {
895 if ((0 == strncmp(c
, dkv
[i
].key
, dkv
[i
].key_len
))) {
896 if ((c
[dkv
[i
].key_len
] == '"') &&
897 (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
+ 1, '"')))) {
898 /* value with "..." */
899 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
+ 1;
903 } else if (NULL
!= (e
= strchr(c
+ dkv
[i
].key_len
, ','))) {
904 /* value without "...", terminated by ',' */
905 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
910 /* value without "...", terminated by EOL */
911 *(dkv
[i
].ptr
) = c
+ dkv
[i
].key_len
;
918 if (p
->conf
.auth_debug
> 1) {
919 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "username", username
);
920 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "realm", realm
);
921 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "nonce", nonce
);
922 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "uri", uri
);
923 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "algorithm", algorithm
);
924 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "qop", qop
);
925 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "cnonce", cnonce
);
926 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "nc", nc
);
927 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "response", respons
);
930 /* check if everything is transmitted */
935 (qop
&& (!nc
|| !cnonce
)) ||
939 log_error_write(srv
, __FILE__
, __LINE__
, "s",
940 "digest: missing field");
947 * protect the md5-sess against missing cnonce and nonce
950 0 == strcasecmp(algorithm
, "md5-sess") &&
951 (!nonce
|| !cnonce
)) {
952 log_error_write(srv
, __FILE__
, __LINE__
, "s",
953 "digest: (md5-sess: missing field");
959 if (qop
&& strcasecmp(qop
, "auth-int") == 0) {
960 log_error_write(srv
, __FILE__
, __LINE__
, "s",
961 "digest: qop=auth-int not supported");
967 m
= get_http_method_name(con
->request
.http_method
);
970 /* password-string == HA1 */
971 password
= buffer_init();
972 username_buf
= buffer_init_string(username
);
973 realm_buf
= buffer_init_string(realm
);
974 if (http_auth_get_password(srv
, p
, username_buf
, realm_buf
, password
)) {
975 buffer_free(password
);
977 buffer_free(username_buf
);
978 buffer_free(realm_buf
);
982 buffer_free(username_buf
);
983 buffer_free(realm_buf
);
985 if (p
->conf
.auth_backend
== AUTH_BACKEND_PLAIN
) {
986 /* generate password from plain-text */
987 li_MD5_Init(&Md5Ctx
);
988 li_MD5_Update(&Md5Ctx
, (unsigned char *)username
, strlen(username
));
989 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
990 li_MD5_Update(&Md5Ctx
, (unsigned char *)realm
, strlen(realm
));
991 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
992 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(password
));
993 li_MD5_Final(HA1
, &Md5Ctx
);
994 } else if (p
->conf
.auth_backend
== AUTH_BACKEND_HTDIGEST
) {
996 /* transform the 32-byte-hex-md5 to a 16-byte-md5 */
997 for (i
= 0; i
< HASHLEN
; i
++) {
998 HA1
[i
] = hex2int(password
->ptr
[i
*2]) << 4;
999 HA1
[i
] |= hex2int(password
->ptr
[i
*2+1]);
1002 /* we already check that above */
1006 buffer_free(password
);
1009 strcasecmp(algorithm
, "md5-sess") == 0) {
1010 li_MD5_Init(&Md5Ctx
);
1011 /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
1013 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
1014 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1015 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
1016 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1017 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
1018 li_MD5_Final(HA1
, &Md5Ctx
);
1023 /* calculate H(A2) */
1024 li_MD5_Init(&Md5Ctx
);
1025 li_MD5_Update(&Md5Ctx
, (unsigned char *)m
, strlen(m
));
1026 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1027 li_MD5_Update(&Md5Ctx
, (unsigned char *)uri
, strlen(uri
));
1028 /* qop=auth-int not supported, already checked above */
1030 if (qop && strcasecmp(qop, "auth-int") == 0) {
1031 li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1032 li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
1035 li_MD5_Final(HA2
, &Md5Ctx
);
1036 CvtHex(HA2
, &HA2Hex
);
1038 /* calculate response */
1039 li_MD5_Init(&Md5Ctx
);
1040 li_MD5_Update(&Md5Ctx
, (unsigned char *)a1
, HASHHEXLEN
);
1041 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1042 li_MD5_Update(&Md5Ctx
, (unsigned char *)nonce
, strlen(nonce
));
1043 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1045 li_MD5_Update(&Md5Ctx
, (unsigned char *)nc
, strlen(nc
));
1046 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1047 li_MD5_Update(&Md5Ctx
, (unsigned char *)cnonce
, strlen(cnonce
));
1048 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1049 li_MD5_Update(&Md5Ctx
, (unsigned char *)qop
, strlen(qop
));
1050 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN(":"));
1052 li_MD5_Update(&Md5Ctx
, (unsigned char *)HA2Hex
, HASHHEXLEN
);
1053 li_MD5_Final(RespHash
, &Md5Ctx
);
1054 CvtHex(RespHash
, &a2
);
1056 if (0 != strcmp(a2
, respons
)) {
1059 if (p
->conf
.auth_debug
) {
1060 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
1061 "digest: digest mismatch", a2
, respons
);
1064 log_error_write(srv
, __FILE__
, __LINE__
, "ssss",
1065 "digest: auth failed for ", username
, ": wrong password, IP:", inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
1071 /* value is our allow-rules */
1072 if (http_auth_match_rules(srv
, req
, username
, NULL
, NULL
)) {
1075 log_error_write(srv
, __FILE__
, __LINE__
, "s",
1076 "digest: rules did match");
1081 /* remember the username */
1082 buffer_copy_string(p
->auth_user
, username
);
1086 if (p
->conf
.auth_debug
) {
1087 log_error_write(srv
, __FILE__
, __LINE__
, "s",
1094 int http_auth_digest_generate_nonce(server
*srv
, mod_auth_plugin_data
*p
, buffer
*fn
, char (*out
)[33]) {
1097 char hh
[LI_ITOSTRING_LENGTH
];
1101 /* generate shared-secret */
1102 li_MD5_Init(&Md5Ctx
);
1103 li_MD5_Update(&Md5Ctx
, CONST_BUF_LEN(fn
));
1104 li_MD5_Update(&Md5Ctx
, CONST_STR_LEN("+"));
1106 /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1107 li_itostrn(hh
, sizeof(hh
), srv
->cur_ts
);
1108 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
1109 li_MD5_Update(&Md5Ctx
, (unsigned char *)srv
->entropy
, sizeof(srv
->entropy
));
1110 li_itostrn(hh
, sizeof(hh
), rand());
1111 li_MD5_Update(&Md5Ctx
, (unsigned char *)hh
, strlen(hh
));
1113 li_MD5_Final(h
, &Md5Ctx
);