4
#include "http_auth_digest.h"
5
#include "inet_ntop_cache.h"
10
#elif defined(__linux__)
11
/* linux needs _XOPEN_SOURCE */
12
# define _XOPEN_SOURCE
19
#include <sys/types.h>
32
# include <openssl/md5.h>
38
* the $apr1$ handling is taken from apache 1.3.x
42
* The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
43
* MD5 crypt() function, which is licenced as follows:
44
* ----------------------------------------------------------------------------
45
* "THE BEER-WARE LICENSE" (Revision 42):
46
* <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
47
* can do whatever you want with this stuff. If we meet some day, and you think
48
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
49
* ----------------------------------------------------------------------------
52
handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
54
static const char base64_pad = '=';
56
/* "A-Z a-z 0-9 + /" maps to 0-63 */
57
static const short base64_reverse_table[256] = {
58
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
59
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 - 0x0F */
60
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 - 0x1F */
61
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 - 0x2F */
62
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 - 0x3F */
63
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40 - 0x4F */
64
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 - 0x5F */
65
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 - 0x6F */
66
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 0x70 - 0x7F */
67
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80 - 0x8F */
68
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90 - 0x9F */
69
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0 - 0xAF */
70
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0 - 0xBF */
71
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0 - 0xCF */
72
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0 - 0xDF */
73
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0 - 0xEF */
74
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xF0 - 0xFF */
78
static unsigned char * base64_decode(buffer *out, const char *in) {
79
unsigned char *result;
83
size_t in_len = strlen(in);
85
buffer_prepare_copy(out, in_len);
87
result = (unsigned char *)out->ptr;
90
/* run through the whole string, converting as we go */
91
for (i = 0; i < in_len; i++) {
94
if (ch == '\0') break;
96
if (ch == base64_pad) break;
98
ch = base64_reverse_table[ch];
106
result[j++] |= ch >> 4;
107
result[j] = (ch & 0x0f) << 4;
110
result[j++] |= ch >>2;
111
result[j] = (ch & 0x03) << 6;
119
/* mop things up if we ended on a boundary */
120
if (ch == base64_pad) {
138
static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) {
141
if (!username->used|| !realm->used) return -1;
143
if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
147
if (buffer_is_empty(p->conf.auth_htdigest_userfile)) return -1;
149
if (0 != stream_open(&f, p->conf.auth_htdigest_userfile)) {
150
log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno));
157
while (f_line - f.start != f.size) {
158
char *f_user, *f_pwd, *e, *f_realm;
159
size_t u_len, pwd_len, r_len;
166
* user:realm:md5(user:realm:password)
169
if (NULL == (f_realm = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
170
log_error_write(srv, __FILE__, __LINE__, "sbs",
171
"parsed error in", p->conf.auth_htdigest_userfile,
172
"expected 'username:realm:hashed password'");
179
if (NULL == (f_pwd = memchr(f_realm + 1, ':', f.size - (f_realm + 1 - f.start)))) {
180
log_error_write(srv, __FILE__, __LINE__, "sbs",
181
"parsed error in", p->conf.auth_plain_userfile,
182
"expected 'username:realm:hashed password'");
189
/* get pointers to the fields */
190
u_len = f_realm - f_user;
192
r_len = f_pwd - f_realm;
195
if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
198
pwd_len = f.size - (f_pwd - f.start);
201
if (username->used - 1 == u_len &&
202
(realm->used - 1 == r_len) &&
203
(0 == strncmp(username->ptr, f_user, u_len)) &&
204
(0 == strncmp(realm->ptr, f_realm, r_len))) {
207
buffer_copy_string_len(password, f_pwd, pwd_len);
220
} else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
221
p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
226
auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile;
228
if (buffer_is_empty(auth_fn)) return -1;
230
if (0 != stream_open(&f, auth_fn)) {
231
log_error_write(srv, __FILE__, __LINE__, "sbss",
232
"opening plain-userfile", auth_fn, "failed:", strerror(errno));
239
while (f_line - f.start != f.size) {
240
char *f_user, *f_pwd, *e;
241
size_t u_len, pwd_len;
248
* user:crypted passwd
251
if (NULL == (f_pwd = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
252
log_error_write(srv, __FILE__, __LINE__, "sbs",
253
"parsed error in", auth_fn,
254
"expected 'username:hashed password'");
261
/* get pointers to the fields */
262
u_len = f_pwd - f_user;
265
if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
268
pwd_len = f.size - (f_pwd - f.start);
271
if (username->used - 1 == u_len &&
272
(0 == strncmp(username->ptr, f_user, u_len))) {
275
buffer_copy_string_len(password, f_pwd, pwd_len);
288
} else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
297
static int http_auth_match_rules(server *srv, mod_auth_plugin_data *p, const char *url, const char *username, const char *group, const char *host) {
298
const char *r = NULL, *rules = NULL;
301
data_string *require;
307
/* check what has to be match to fullfil the request */
308
/* search auth-directives for path */
309
for (i = 0; i < p->conf.auth_require->used; i++) {
310
if (p->conf.auth_require->data[i]->key->used == 0) continue;
312
if (0 == strncmp(url, p->conf.auth_require->data[i]->key->ptr, p->conf.auth_require->data[i]->key->used - 1)) {
317
if (i == p->conf.auth_require->used) {
321
req = ((data_array *)(p->conf.auth_require->data[i]))->value;
323
require = (data_string *)array_get_element(req, "require");
325
/* if we get here, the user we got a authed user */
326
if (0 == strcmp(require->value->ptr, "valid-user")) {
330
/* user=name1|group=name3|host=name4 */
332
/* seperate the string by | */
334
log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value);
337
username_len = username ? strlen(username) : 0;
339
r = rules = require->value->ptr;
343
const char *k, *v, *e;
344
int k_len, v_len, r_len;
351
r_len = strlen(rules) - (r - rules);
354
/* from r to r + r_len is a rule */
356
if (0 == strncmp(r, "valid-user", r_len)) {
357
log_error_write(srv, __FILE__, __LINE__, "sb",
358
"parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
363
/* search for = in the rules */
364
if (NULL == (eq = strchr(r, '='))) {
365
log_error_write(srv, __FILE__, __LINE__, "sb",
366
"parsing the 'require' section in 'auth.require' failed: a = is missing",
372
if (eq > r + r_len) {
373
log_error_write(srv, __FILE__, __LINE__, "sb",
374
"parsing the 'require' section in 'auth.require' failed: = out of range",
380
/* the part before the = is user|group|host */
385
v_len = r_len - k_len - 1;
388
if (0 == strncmp(k, "user", k_len)) {
390
username_len == v_len &&
391
0 == strncmp(username, v, v_len)) {
394
} else if (0 == strncmp(k, "host", k_len)) {
395
log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)");
397
log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
400
} else if (k_len == 5) {
401
if (0 == strncmp(k, "group", k_len)) {
402
log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)");
404
log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k);
408
log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
416
log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched");
421
#define APR_MD5_DIGESTSIZE 16
422
#define APR1_ID "$apr1$"
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.
430
static void to64(char *s, unsigned long v, int n)
432
static unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */
433
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
436
*s++ = itoa64[v&0x3f];
441
static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
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.
448
char passwd[120], *p;
450
unsigned char final[APR_MD5_DIGESTSIZE];
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.
463
* If it starts with the magic string, then skip that.
465
if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) {
466
sp += strlen(APR1_ID);
470
* It stops at the first '$' or 8 chars, whichever comes first
472
for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
477
* Get the length of the true salt
482
* 'Time to make the doughnuts..'
487
* The password first, since that is what is most unknown
489
MD5_Update(&ctx, pw, strlen(pw));
492
* Then our magic string
494
MD5_Update(&ctx, APR1_ID, strlen(APR1_ID));
499
MD5_Update(&ctx, sp, sl);
502
* Then just as many characters of the MD5(pw, salt, pw)
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);
515
* Don't leave anything around in vm they could use.
517
memset(final, 0, sizeof(final));
520
* Then something really weird...
522
for (i = strlen(pw); i != 0; i >>= 1) {
524
MD5_Update(&ctx, final, 1);
527
MD5_Update(&ctx, pw, 1);
532
* Now make the output string. We know our limitations, so we
533
* can use the string routines without bounds checking.
535
strcpy(passwd, APR1_ID);
536
strncat(passwd, sp, sl);
539
MD5_Final(final, &ctx);
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...
546
for (i = 0; i < 1000; i++) {
549
MD5_Update(&ctx1, pw, strlen(pw));
552
MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
555
MD5_Update(&ctx1, sp, sl);
559
MD5_Update(&ctx1, pw, strlen(pw));
563
MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
566
MD5_Update(&ctx1, pw, strlen(pw));
568
MD5_Final(final,&ctx1);
571
p = passwd + strlen(passwd);
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;
582
* Don't leave anything around in vm they could use.
584
memset(final, 0, sizeof(final));
588
#define apr_cpystrn strncpy
589
apr_cpystrn(result, passwd, nbytes - 1);
596
* @param password password-string from the auth-backend
597
* @param pw password-string from the client
600
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) {
604
if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
608
* user:realm:md5(user:realm:password)
616
MD5_Update(&Md5Ctx, (unsigned char *)username->ptr, username->used - 1);
617
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
618
MD5_Update(&Md5Ctx, (unsigned char *)realm->ptr, realm->used - 1);
619
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
620
MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
621
MD5_Final(HA1, &Md5Ctx);
625
if (0 == strcmp(password->ptr, a1)) {
628
} else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
630
if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) {
632
* The hash was created using $apr1$ custom algorithm.
634
apr_md5_encode(pw, password->ptr, sample, sizeof(sample));
635
return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
644
* user:crypted password
649
* CRYPT_STD_DES 2-character (Default)
650
* CRYPT_EXT_DES 9-character
651
* CRYPT_MD5 12-character beginning with $1$
652
* CRYPT_BLOWFISH 16-character beginning with $2$
655
if (password->used < 13 + 1) {
659
if (password->used == 13 + 1) {
660
/* a simple DES password is 2 + 11 characters */
662
} else if (password->ptr[0] == '$' && password->ptr[2] == '$') {
665
if (NULL == (dollar = strchr(password->ptr + 3, '$'))) {
669
salt_len = dollar - password->ptr;
672
if (salt_len > sizeof(salt) - 1) {
676
strncpy(salt, password->ptr, salt_len);
678
salt[salt_len] = '\0';
680
crypted = crypt(pw, salt);
682
if (0 == strcmp(password->ptr, crypted)) {
688
} else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
689
if (0 == strcmp(password->ptr, pw)) {
692
} else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
695
LDAPMessage *lm, *first;
698
char *attrs[] = { LDAP_NO_ATTRS, NULL };
701
/* for now we stay synchronous */
704
* 1. connect anonymously (done in plugin init)
705
* 2. get DN for uid = username
706
* 3. auth against ldap server
707
* 4. (optional) check a field
714
* we have to protect us againt username which modifies out filter in
718
for (i = 0; i < username->used - 1; i++) {
719
char c = username->ptr[i];
729
log_error_write(srv, __FILE__, __LINE__, "sbd",
730
"ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username, i);
736
if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0')
740
buffer_copy_string_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
741
buffer_append_string_buffer(p->ldap_filter, username);
742
buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post);
746
if (p->anon_conf->ldap == NULL ||
747
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))) {
749
/* try again; the ldap library sometimes fails for the first call but reconnects */
750
if (p->anon_conf->ldap == NULL || ret != LDAP_SERVER_DOWN ||
751
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))) {
753
if (auth_ldap_init(srv, p->anon_conf) != HANDLER_GO_ON)
756
if (p->anon_conf->ldap == NULL ||
757
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))) {
758
log_error_write(srv, __FILE__, __LINE__, "sssb",
759
"ldap:", ldap_err2string(ret), "filter:", p->ldap_filter);
765
if (NULL == (first = ldap_first_entry(p->anon_conf->ldap, lm))) {
766
log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
773
if (NULL == (dn = ldap_get_dn(p->anon_conf->ldap, first))) {
774
log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
785
if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) {
786
log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
791
if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
792
log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
799
if (p->conf.auth_ldap_starttls == 1) {
800
if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(ldap, NULL, NULL))) {
801
log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret));
810
if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) {
811
log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
821
/* everything worked, good, access granted */
829
int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
830
buffer *username, *password;
835
realm = (data_string *)array_get_element(req, "realm");
837
username = buffer_init();
839
if (!base64_decode(username, realm_str)) {
840
log_error_write(srv, __FILE__, __LINE__, "sb", "decodeing base64-string failed", username);
842
buffer_free(username);
846
/* r2 == user:password */
847
if (NULL == (pw = strchr(username->ptr, ':'))) {
848
log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username);
850
buffer_free(username);
856
username->used = pw - username->ptr;
858
password = buffer_init();
859
/* copy password to r1 */
860
if (http_auth_get_password(srv, p, username, realm->value, password)) {
861
buffer_free(username);
862
buffer_free(password);
864
if (AUTH_BACKEND_UNSET == p->conf.auth_backend) {
865
log_error_write(srv, __FILE__, __LINE__, "s", "auth.backend is not set");
867
log_error_write(srv, __FILE__, __LINE__, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
873
/* password doesn't match */
874
if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) {
875
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)));
877
buffer_free(username);
878
buffer_free(password);
883
/* value is our allow-rules */
884
if (http_auth_match_rules(srv, p, url->ptr, username->ptr, NULL, NULL)) {
885
buffer_free(username);
886
buffer_free(password);
888
log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match");
893
/* remember the username */
894
buffer_copy_string_buffer(p->auth_user, username);
896
buffer_free(username);
897
buffer_free(password);
908
int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
912
char *username = NULL;
916
char *algorithm = NULL;
920
char *respons = NULL;
923
const char *m = NULL;
925
buffer *password, *b, *username_buf, *realm_buf;
937
digest_kv dkv[10] = {
952
dkv[0].ptr = &username;
956
dkv[4].ptr = &algorithm;
958
dkv[6].ptr = &cnonce;
960
dkv[8].ptr = &respons;
964
if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST &&
965
p->conf.auth_backend != AUTH_BACKEND_PLAIN) {
966
log_error_write(srv, __FILE__, __LINE__, "s",
967
"digest: unsupported backend (only htdigest or plain)");
972
b = buffer_init_string(realm_str);
974
/* parse credentials from client */
975
for (c = b->ptr; *c; c++) {
976
/* skip whitespaces */
977
while (*c == ' ' || *c == '\t') c++;
980
for (i = 0; dkv[i].key; i++) {
981
if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
982
if ((c[dkv[i].key_len] == '"') &&
983
(NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
984
/* value with "..." */
985
*(dkv[i].ptr) = c + dkv[i].key_len + 1;
989
} else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
990
/* value without "...", terminated by ',' */
991
*(dkv[i].ptr) = c + dkv[i].key_len;
996
/* value without "...", terminated by EOL */
997
*(dkv[i].ptr) = c + dkv[i].key_len;
1004
if (p->conf.auth_debug > 1) {
1005
log_error_write(srv, __FILE__, __LINE__, "ss", "username", username);
1006
log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm);
1007
log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce);
1008
log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri);
1009
log_error_write(srv, __FILE__, __LINE__, "ss", "algorigthm", algorithm);
1010
log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop);
1011
log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce);
1012
log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc);
1013
log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons);
1016
/* check if everything is transmitted */
1021
(qop && (!nc || !cnonce)) ||
1025
log_error_write(srv, __FILE__, __LINE__, "s",
1026
"digest: missing field");
1033
* protect the md5-sess against missing cnonce and nonce
1036
0 == strcasecmp(algorithm, "md5-sess") &&
1037
(!nonce || !cnonce)) {
1038
log_error_write(srv, __FILE__, __LINE__, "s",
1039
"digest: (md5-sess: missing field");
1045
m = get_http_method_name(con->request.http_method);
1047
/* password-string == HA1 */
1048
password = buffer_init();
1049
username_buf = buffer_init_string(username);
1050
realm_buf = buffer_init_string(realm);
1051
if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) {
1052
buffer_free(password);
1054
buffer_free(username_buf);
1055
buffer_free(realm_buf);
1059
buffer_free(username_buf);
1060
buffer_free(realm_buf);
1062
if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
1063
/* generate password from plain-text */
1065
MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username));
1066
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1067
MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
1068
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1069
MD5_Update(&Md5Ctx, (unsigned char *)password->ptr, password->used - 1);
1070
MD5_Final(HA1, &Md5Ctx);
1071
} else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
1073
/* transform the 32-byte-hex-md5 to a 16-byte-md5 */
1074
for (i = 0; i < HASHLEN; i++) {
1075
HA1[i] = hex2int(password->ptr[i*2]) << 4;
1076
HA1[i] |= hex2int(password->ptr[i*2+1]);
1079
/* we already check that above */
1083
buffer_free(password);
1086
strcasecmp(algorithm, "md5-sess") == 0) {
1088
MD5_Update(&Md5Ctx, (unsigned char *)HA1, 16);
1089
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1090
MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1091
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1092
MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1093
MD5_Final(HA1, &Md5Ctx);
1098
/* calculate H(A2) */
1100
MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
1101
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1102
MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
1103
if (qop && strcasecmp(qop, "auth-int") == 0) {
1104
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1105
MD5_Update(&Md5Ctx, (unsigned char *)"", HASHHEXLEN);
1107
MD5_Final(HA2, &Md5Ctx);
1108
CvtHex(HA2, HA2Hex);
1110
/* calculate response */
1112
MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
1113
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1114
MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1115
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1117
MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
1118
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1119
MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1120
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1121
MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
1122
MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1124
MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
1125
MD5_Final(RespHash, &Md5Ctx);
1126
CvtHex(RespHash, a2);
1128
if (0 != strcmp(a2, respons)) {
1131
if (p->conf.auth_debug) {
1132
log_error_write(srv, __FILE__, __LINE__, "sss",
1133
"digest: digest mismatch", a2, respons);
1136
log_error_write(srv, __FILE__, __LINE__, "ssss",
1137
"digest: auth failed for ", username, ": wrong password, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
1143
/* value is our allow-rules */
1144
if (http_auth_match_rules(srv, p, url->ptr, username, NULL, NULL)) {
1147
log_error_write(srv, __FILE__, __LINE__, "s",
1148
"digest: rules did match");
1153
/* remember the username */
1154
buffer_copy_string(p->auth_user, username);
1158
if (p->conf.auth_debug) {
1159
log_error_write(srv, __FILE__, __LINE__, "s",
1166
int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char out[33]) {
1173
/* generate shared-secret */
1175
MD5_Update(&Md5Ctx, (unsigned char *)fn->ptr, fn->used - 1);
1176
MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
1178
/* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1179
LI_ltostr(hh, srv->cur_ts);
1180
MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1181
MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy));
1182
LI_ltostr(hh, rand());
1183
MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1185
MD5_Final(h, &Md5Ctx);