Annotation of embedaddon/lighttpd/src/http_auth.c, revision 1.1.1.3
1.1.1.3 ! misho 1: #include "first.h"
! 2:
1.1 misho 3: #include "server.h"
4: #include "log.h"
5: #include "http_auth.h"
6: #include "inet_ntop_cache.h"
7: #include "stream.h"
1.1.1.3 ! misho 8: #include "base64.h"
1.1 misho 9:
10: #ifdef HAVE_CRYPT_H
11: # include <crypt.h>
12: #elif defined(__linux__)
13: /* linux needs _XOPEN_SOURCE */
14: # define _XOPEN_SOURCE
15: #endif
16:
1.1.1.3 ! misho 17: #if defined(HAVE_LIBCRYPT) && !defined(HAVE_CRYPT)
! 18: /* always assume crypt() is present if we have -lcrypt */
1.1 misho 19: # define HAVE_CRYPT
20: #endif
21:
22: #include <sys/types.h>
23: #include <sys/stat.h>
24:
25: #include <fcntl.h>
26: #include <stdlib.h>
27: #include <stdio.h>
28: #include <string.h>
29: #include <time.h>
30: #include <errno.h>
31: #include <unistd.h>
32: #include <ctype.h>
1.1.1.3 ! misho 33: #include <mysql/mysql.h>
1.1 misho 34:
35: #include "md5.h"
36:
37: #ifdef USE_OPENSSL
38: #include <openssl/sha.h>
39: #endif
40:
1.1.1.3 ! misho 41: #include "safe_memclear.h"
! 42:
1.1 misho 43: #define HASHLEN 16
44: #define HASHHEXLEN 32
45: typedef unsigned char HASH[HASHLEN];
46: typedef char HASHHEX[HASHHEXLEN+1];
47:
1.1.1.3 ! misho 48: static void CvtHex(const HASH Bin, char (*Hex)[33]) {
! 49: li_tohex(*Hex, sizeof(*Hex), (const char*) Bin, 16);
1.1 misho 50: }
51:
1.1.1.3 ! misho 52:
1.1 misho 53: /**
54: * the $apr1$ handling is taken from apache 1.3.x
55: */
56:
57: /*
58: * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
59: * MD5 crypt() function, which is licenced as follows:
60: * ----------------------------------------------------------------------------
61: * "THE BEER-WARE LICENSE" (Revision 42):
62: * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
63: * can do whatever you want with this stuff. If we meet some day, and you think
64: * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
65: * ----------------------------------------------------------------------------
66: */
67:
68: handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
69:
70: static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) {
1.1.1.3 ! misho 71: if (buffer_is_empty(username) || buffer_is_empty(realm)) return -1;
1.1 misho 72:
73: if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
1.1.1.3 ! misho 74: FILE *fp;
! 75: char f_user[1024];
1.1 misho 76:
1.1.1.3 ! misho 77: if (buffer_string_is_empty(p->conf.auth_htdigest_userfile)) return -1;
1.1 misho 78:
1.1.1.3 ! misho 79: fp = fopen(p->conf.auth_htdigest_userfile->ptr, "r");
! 80: if (NULL == fp) {
1.1 misho 81: log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno));
82:
83: return -1;
84: }
85:
1.1.1.3 ! misho 86: while (NULL != fgets(f_user, sizeof(f_user), fp)) {
! 87: char *f_pwd, *f_realm;
! 88: size_t u_len, r_len;
1.1 misho 89:
1.1.1.3 ! misho 90: /* skip blank lines and comment lines (beginning '#') */
! 91: if (f_user[0] == '#' || f_user[0] == '\n' || f_user[0] == '\0') continue;
1.1 misho 92:
93: /*
94: * htdigest format
95: *
96: * user:realm:md5(user:realm:password)
97: */
98:
1.1.1.3 ! misho 99: if (NULL == (f_realm = strchr(f_user, ':'))) {
1.1 misho 100: log_error_write(srv, __FILE__, __LINE__, "sbs",
101: "parsed error in", p->conf.auth_htdigest_userfile,
102: "expected 'username:realm:hashed password'");
103:
1.1.1.3 ! misho 104: continue; /* skip bad lines */
1.1 misho 105: }
106:
1.1.1.3 ! misho 107: if (NULL == (f_pwd = strchr(f_realm + 1, ':'))) {
1.1 misho 108: log_error_write(srv, __FILE__, __LINE__, "sbs",
109: "parsed error in", p->conf.auth_plain_userfile,
110: "expected 'username:realm:hashed password'");
111:
1.1.1.3 ! misho 112: continue; /* skip bad lines */
1.1 misho 113: }
114:
115: /* get pointers to the fields */
116: u_len = f_realm - f_user;
117: f_realm++;
118: r_len = f_pwd - f_realm;
119: f_pwd++;
120:
1.1.1.3 ! misho 121: if (buffer_string_length(username) == u_len &&
! 122: (buffer_string_length(realm) == r_len) &&
1.1 misho 123: (0 == strncmp(username->ptr, f_user, u_len)) &&
124: (0 == strncmp(realm->ptr, f_realm, r_len))) {
125: /* found */
126:
1.1.1.3 ! misho 127: size_t pwd_len = strlen(f_pwd);
! 128: if (f_pwd[pwd_len-1] == '\n') --pwd_len;
! 129:
1.1 misho 130: buffer_copy_string_len(password, f_pwd, pwd_len);
131:
1.1.1.3 ! misho 132: fclose(fp);
! 133: return 0;
1.1 misho 134: }
135: }
136:
1.1.1.3 ! misho 137: fclose(fp);
1.1 misho 138: } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
139: p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
1.1.1.3 ! misho 140: FILE *fp;
! 141: char f_user[1024];
1.1 misho 142: buffer *auth_fn;
143:
144: auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile;
145:
1.1.1.3 ! misho 146: if (buffer_string_is_empty(auth_fn)) return -1;
1.1 misho 147:
1.1.1.3 ! misho 148: fp = fopen(auth_fn->ptr, "r");
! 149: if (NULL == fp) {
1.1 misho 150: log_error_write(srv, __FILE__, __LINE__, "sbss",
151: "opening plain-userfile", auth_fn, "failed:", strerror(errno));
152:
153: return -1;
154: }
155:
1.1.1.3 ! misho 156: while (NULL != fgets(f_user, sizeof(f_user), fp)) {
! 157: char *f_pwd;
! 158: size_t u_len;
1.1 misho 159:
1.1.1.3 ! misho 160: /* skip blank lines and comment lines (beginning '#') */
! 161: if (f_user[0] == '#' || f_user[0] == '\n' || f_user[0] == '\0') continue;
1.1 misho 162:
163: /*
164: * htpasswd format
165: *
166: * user:crypted passwd
167: */
168:
1.1.1.3 ! misho 169: if (NULL == (f_pwd = strchr(f_user, ':'))) {
1.1 misho 170: log_error_write(srv, __FILE__, __LINE__, "sbs",
171: "parsed error in", auth_fn,
172: "expected 'username:hashed password'");
173:
1.1.1.3 ! misho 174: continue; /* skip bad lines */
1.1 misho 175: }
176:
177: /* get pointers to the fields */
178: u_len = f_pwd - f_user;
179: f_pwd++;
180:
1.1.1.3 ! misho 181: if (buffer_string_length(username) == u_len &&
1.1 misho 182: (0 == strncmp(username->ptr, f_user, u_len))) {
183: /* found */
184:
1.1.1.3 ! misho 185: size_t pwd_len = strlen(f_pwd);
! 186: if (f_pwd[pwd_len-1] == '\n') --pwd_len;
! 187:
1.1 misho 188: buffer_copy_string_len(password, f_pwd, pwd_len);
189:
1.1.1.3 ! misho 190: fclose(fp);
! 191: return 0;
1.1 misho 192: }
193: }
194:
1.1.1.3 ! misho 195: fclose(fp);
1.1 misho 196: } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
1.1.1.3 ! misho 197: return 0;
! 198: } else if (p->conf.auth_backend == AUTH_BACKEND_MYSQL) {
! 199: MYSQL_RES *result;
! 200: MYSQL_ROW row;
! 201: int port = atoi(p->conf.auth_mysql_port->ptr);
! 202: char q[255];
! 203:
! 204: if (p->conf.auth_mysql_socket->ptr != NULL)
! 205: if (0 == strcmp(p->conf.auth_mysql_socket->ptr, "")) p->conf.auth_mysql_socket->ptr = NULL;
! 206:
! 207: p->conf.mysql_conn = mysql_init(NULL);
! 208:
! 209: if (mysql_real_connect(p->conf.mysql_conn, p->conf.auth_mysql_host->ptr, p->conf.auth_mysql_user->ptr, p->conf.auth_mysql_pass->ptr, p->conf.auth_mysql_db->ptr, port, p->conf.auth_mysql_socket->ptr, 0))
! 210: {
! 211: //#define MY_HOSTING
! 212:
! 213: #ifdef MY_HOSTING
! 214: char my_full_realm[255];
! 215: char *my_realm = NULL;
! 216: char *my_domain = NULL;
! 217:
! 218: char *uname;
! 219: size_t unamelen;
! 220:
! 221: unamelen = strlen(username->ptr);
! 222: uname = malloc(unamelen*2+1);
! 223:
! 224: mysql_real_escape_string(p->conf.mysql_conn,
! 225: uname, username->ptr,
! 226: (unsigned long)unamelen);
! 227:
! 228: strcpy(my_full_realm, realm->ptr);
! 229: my_realm = strtok(my_full_realm, "@");
! 230:
! 231: if (my_realm != NULL)
! 232: my_domain = strtok(NULL, "@");
! 233:
! 234: sprintf(q, "SELECT %s FROM %s, %s WHERE %s='%s' AND %s='%s' AND %s='%s' AND %s=%s",
! 235: p->conf.auth_mysql_col_pass->ptr,
! 236:
! 237: p->conf.auth_mysql_users_table->ptr,
! 238: p->conf.auth_mysql_domains_table->ptr,
! 239:
! 240: p->conf.auth_mysql_col_user->ptr,
! 241: uname,
! 242:
! 243: p->conf.auth_mysql_col_realm->ptr,
! 244: my_realm,
! 245:
! 246: p->conf.auth_mysql_col_domain->ptr,
! 247: my_domain,
! 248:
! 249: p->conf.auth_mysql_domains_table_col_domain_id->ptr,
! 250: p->conf.auth_mysql_users_table_col_domain_id->ptr
! 251: );
! 252:
! 253: free(uname);
! 254: #else
! 255: // sanitize username & realm by taguchi@ff.iij4u.or.jp
! 256: char *uname, *urealm;
! 257: size_t unamelen, urealmlen;
! 258:
! 259: unamelen = strlen(username->ptr);
! 260: urealmlen = strlen(realm->ptr);
! 261: uname = malloc(unamelen*2+1);
! 262: urealm = malloc(urealmlen*2+1);
! 263:
! 264: mysql_real_escape_string(p->conf.mysql_conn,
! 265: uname, username->ptr,
! 266: (unsigned long)unamelen);
! 267:
! 268: mysql_real_escape_string(p->conf.mysql_conn,
! 269: urealm, realm->ptr,
! 270: (unsigned long)unamelen);
! 271:
! 272: mysql_real_escape_string(p->conf.mysql_conn,
! 273: urealm, realm->ptr,
! 274: (unsigned long)urealmlen);
! 275:
! 276: sprintf(q, "SELECT %s FROM %s WHERE %s='%s' AND %s='%s'",
! 277: p->conf.auth_mysql_col_pass->ptr,
! 278: p->conf.auth_mysql_users_table->ptr,
! 279: p->conf.auth_mysql_col_user->ptr,
! 280: uname,
! 281: p->conf.auth_mysql_col_realm->ptr,
! 282: urealm
! 283: );
! 284:
! 285: free(uname);
! 286: free(urealm);
! 287: #endif
1.1 misho 288:
1.1.1.3 ! misho 289: mysql_query(p->conf.mysql_conn, q);
! 290: result = mysql_store_result(p->conf.mysql_conn);
! 291: if (mysql_num_rows(result) == 1)
! 292: {
! 293: /* found */
! 294: row = mysql_fetch_row(result);
! 295: buffer_copy_string_len(password, row[0], strlen(row[0]));
! 296:
! 297: return 0;
! 298: } else
! 299: {
! 300: /* not found */
! 301: return -1;
! 302: }
! 303:
! 304: mysql_free_result(result);
! 305: mysql_close(p->conf.mysql_conn);
! 306:
! 307: p->conf.mysql_conn = NULL;
! 308: } else
! 309: return -1;
! 310: }
1.1 misho 311: }
312:
313: int http_auth_match_rules(server *srv, array *req, const char *username, const char *group, const char *host) {
314: const char *r = NULL, *rules = NULL;
315: int username_len;
316: data_string *require;
317:
318: UNUSED(group);
319: UNUSED(host);
320:
321: require = (data_string *)array_get_element(req, "require");
1.1.1.3 ! misho 322: if (!require) return -1; /*(should not happen; config is validated at startup)*/
1.1 misho 323:
324: /* if we get here, the user we got a authed user */
325: if (0 == strcmp(require->value->ptr, "valid-user")) {
326: return 0;
327: }
328:
329: /* user=name1|group=name3|host=name4 */
330:
331: /* seperate the string by | */
332: #if 0
333: log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value);
334: #endif
335:
336: username_len = username ? strlen(username) : 0;
337:
338: r = rules = require->value->ptr;
339:
340: while (1) {
341: const char *eq;
342: const char *k, *v, *e;
343: int k_len, v_len, r_len;
344:
345: e = strchr(r, '|');
346:
347: if (e) {
348: r_len = e - r;
349: } else {
350: r_len = strlen(rules) - (r - rules);
351: }
352:
353: /* from r to r + r_len is a rule */
354:
355: if (0 == strncmp(r, "valid-user", r_len)) {
356: log_error_write(srv, __FILE__, __LINE__, "sb",
357: "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
358: require->value);
359: return -1;
360: }
361:
362: /* search for = in the rules */
363: if (NULL == (eq = strchr(r, '='))) {
364: log_error_write(srv, __FILE__, __LINE__, "sb",
365: "parsing the 'require' section in 'auth.require' failed: a = is missing",
366: require->value);
367: return -1;
368: }
369:
370: /* = out of range */
371: if (eq > r + r_len) {
372: log_error_write(srv, __FILE__, __LINE__, "sb",
373: "parsing the 'require' section in 'auth.require' failed: = out of range",
374: require->value);
375:
376: return -1;
377: }
378:
379: /* the part before the = is user|group|host */
380:
381: k = r;
382: k_len = eq - r;
383: v = eq + 1;
384: v_len = r_len - k_len - 1;
385:
386: if (k_len == 4) {
387: if (0 == strncmp(k, "user", k_len)) {
388: if (username &&
389: username_len == v_len &&
390: 0 == strncmp(username, v, v_len)) {
391: return 0;
392: }
393: } else if (0 == strncmp(k, "host", k_len)) {
394: log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)");
395: } else {
396: log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
397: return -1;
398: }
399: } else if (k_len == 5) {
400: if (0 == strncmp(k, "group", k_len)) {
401: log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)");
402: } else {
403: log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k);
404: return -1;
405: }
406: } else {
407: log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
408: return -1;
409: }
410:
411: if (!e) break;
412: r = e + 1;
413: }
414:
415: log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched");
416:
417: return -1;
418: }
419:
420: #define APR_MD5_DIGESTSIZE 16
421: #define APR1_ID "$apr1$"
422:
423: /*
424: * The following MD5 password encryption code was largely borrowed from
425: * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is
426: * licenced as stated at the top of this file.
427: */
428:
429: static void to64(char *s, unsigned long v, int n)
430: {
1.1.1.3 ! misho 431: static const unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */
! 432: "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1.1 misho 433:
1.1.1.3 ! misho 434: while (--n >= 0) {
! 435: *s++ = itoa64[v&0x3f];
! 436: v >>= 6;
! 437: }
1.1 misho 438: }
439:
440: static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
1.1.1.3 ! misho 441: /*
! 442: * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
! 443: * plus 4 for the '$' separators, plus the password hash itself.
! 444: * Let's leave a goodly amount of leeway.
! 445: */
! 446:
! 447: char passwd[120], *p;
! 448: const char *sp, *ep;
! 449: unsigned char final[APR_MD5_DIGESTSIZE];
! 450: ssize_t sl, pl, i;
! 451: li_MD5_CTX ctx, ctx1;
! 452: unsigned long l;
! 453:
! 454: /*
! 455: * Refine the salt first. It's possible we were given an already-hashed
! 456: * string as the salt argument, so extract the actual salt value from it
! 457: * if so. Otherwise just use the string up to the first '$' as the salt.
! 458: */
! 459: sp = salt;
! 460:
! 461: /*
! 462: * If it starts with the magic string, then skip that.
! 463: */
! 464: if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) {
! 465: sp += strlen(APR1_ID);
! 466: }
! 467:
! 468: /*
! 469: * It stops at the first '$' or 8 chars, whichever comes first
! 470: */
! 471: for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
! 472: continue;
! 473: }
! 474:
! 475: /*
! 476: * Get the length of the true salt
! 477: */
! 478: sl = ep - sp;
! 479:
! 480: /*
! 481: * 'Time to make the doughnuts..'
! 482: */
! 483: li_MD5_Init(&ctx);
! 484:
! 485: /*
! 486: * The password first, since that is what is most unknown
! 487: */
! 488: li_MD5_Update(&ctx, pw, strlen(pw));
! 489:
! 490: /*
! 491: * Then our magic string
! 492: */
! 493: li_MD5_Update(&ctx, APR1_ID, strlen(APR1_ID));
! 494:
! 495: /*
! 496: * Then the raw salt
! 497: */
! 498: li_MD5_Update(&ctx, sp, sl);
! 499:
! 500: /*
! 501: * Then just as many characters of the MD5(pw, salt, pw)
! 502: */
! 503: li_MD5_Init(&ctx1);
! 504: li_MD5_Update(&ctx1, pw, strlen(pw));
! 505: li_MD5_Update(&ctx1, sp, sl);
! 506: li_MD5_Update(&ctx1, pw, strlen(pw));
! 507: li_MD5_Final(final, &ctx1);
! 508: for (pl = strlen(pw); pl > 0; pl -= APR_MD5_DIGESTSIZE) {
! 509: li_MD5_Update(
! 510: &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: li_MD5_Update(&ctx, final, 1);
! 525: }
! 526: else {
! 527: li_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: li_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: li_MD5_Init(&ctx1);
! 548: if (i & 1) {
! 549: li_MD5_Update(&ctx1, pw, strlen(pw));
! 550: }
! 551: else {
! 552: li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
! 553: }
! 554: if (i % 3) {
! 555: li_MD5_Update(&ctx1, sp, sl);
! 556: }
! 557:
! 558: if (i % 7) {
! 559: li_MD5_Update(&ctx1, pw, strlen(pw));
! 560: }
! 561:
! 562: if (i & 1) {
! 563: li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
! 564: }
! 565: else {
! 566: li_MD5_Update(&ctx1, pw, strlen(pw));
! 567: }
! 568: li_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: safe_memclear(final, sizeof(final));
1.1 misho 585:
586: /* FIXME
587: */
588: #define apr_cpystrn strncpy
1.1.1.3 ! misho 589: apr_cpystrn(result, passwd, nbytes - 1);
1.1 misho 590: }
591:
592: #ifdef USE_OPENSSL
593: static void apr_sha_encode(const char *pw, char *result, size_t nbytes) {
1.1.1.3 ! misho 594: unsigned char digest[20];
! 595: size_t base64_written;
! 596:
! 597: SHA1((const unsigned char*) pw, strlen(pw), digest);
1.1 misho 598:
599: memset(result, 0, nbytes);
600:
601: /* need 5 bytes for "{SHA}", 28 for base64 (3 bytes -> 4 bytes) of SHA1 (20 bytes), 1 terminating */
602: if (nbytes < 5 + 28 + 1) return;
603:
1.1.1.3 ! misho 604: memcpy(result, "{SHA}", 5);
! 605: base64_written = li_to_base64(result + 5, nbytes - 5, digest, 20, BASE64_STANDARD);
! 606: force_assert(base64_written == 28);
! 607: result[5 + base64_written] = '\0'; /* terminate string */
1.1 misho 608: }
609: #endif
610:
611: /**
612: *
613: *
614: * @param password password-string from the auth-backend
615: * @param pw password-string from the client
616: */
617:
618: 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) {
619: UNUSED(srv);
620: UNUSED(req);
621:
622: if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
623: /*
624: * htdigest format
625: *
626: * user:realm:md5(user:realm:password)
627: */
628:
629: li_MD5_CTX Md5Ctx;
630: HASH HA1;
1.1.1.3 ! misho 631: char a1[33];
1.1 misho 632:
633: li_MD5_Init(&Md5Ctx);
1.1.1.3 ! misho 634: li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(username));
! 635: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
! 636: li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(realm));
! 637: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 638: li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
639: li_MD5_Final(HA1, &Md5Ctx);
640:
1.1.1.3 ! misho 641: CvtHex(HA1, &a1);
1.1 misho 642:
643: if (0 == strcmp(password->ptr, a1)) {
644: return 0;
645: }
646: } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
647: char sample[120];
648: if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) {
649: /*
650: * The hash was created using $apr1$ custom algorithm.
651: */
652: apr_md5_encode(pw, password->ptr, sample, sizeof(sample));
653: return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
654: #ifdef USE_OPENSSL
655: } else if (0 == strncmp(password->ptr, "{SHA}", 5)) {
656: apr_sha_encode(pw, sample, sizeof(sample));
657: return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
658: #endif
659: } else {
1.1.1.3 ! misho 660: #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
1.1 misho 661: char *crypted;
1.1.1.3 ! misho 662: #if defined(HAVE_CRYPT_R)
! 663: struct crypt_data crypt_tmp_data;
! 664: crypt_tmp_data.initialized = 0;
! 665: #endif
1.1 misho 666:
667: /* a simple DES password is 2 + 11 characters. everything else should be longer. */
1.1.1.3 ! misho 668: if (buffer_string_length(password) < 13) {
1.1 misho 669: return -1;
670: }
671:
1.1.1.3 ! misho 672: #if defined(HAVE_CRYPT_R)
! 673: if (0 == (crypted = crypt_r(pw, password->ptr, &crypt_tmp_data))) {
! 674: #else
1.1 misho 675: if (0 == (crypted = crypt(pw, password->ptr))) {
1.1.1.3 ! misho 676: #endif
1.1 misho 677: /* crypt failed. */
678: return -1;
679: }
680:
681: if (0 == strcmp(password->ptr, crypted)) {
682: return 0;
683: }
684: #endif
685: }
686: } else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
687: if (0 == strcmp(password->ptr, pw)) {
688: return 0;
689: }
690: } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
691: #ifdef USE_LDAP
692: LDAP *ldap;
693: LDAPMessage *lm, *first;
694: char *dn;
695: int ret;
696: char *attrs[] = { LDAP_NO_ATTRS, NULL };
1.1.1.3 ! misho 697: size_t i, len;
1.1 misho 698:
699: /* for now we stay synchronous */
700:
701: /*
702: * 1. connect anonymously (done in plugin init)
703: * 2. get DN for uid = username
704: * 3. auth against ldap server
705: * 4. (optional) check a field
706: * 5. disconnect
707: *
708: */
709:
710: /* check username
711: *
712: * we have to protect us againt username which modifies out filter in
713: * a unpleasant way
714: */
715:
1.1.1.3 ! misho 716: len = buffer_string_length(username);
! 717: for (i = 0; i < len; i++) {
1.1 misho 718: char c = username->ptr[i];
719:
720: if (!isalpha(c) &&
721: !isdigit(c) &&
722: (c != ' ') &&
723: (c != '@') &&
724: (c != '-') &&
725: (c != '_') &&
726: (c != '.') ) {
727:
728: log_error_write(srv, __FILE__, __LINE__, "sbd",
729: "ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username, i);
730:
731: return -1;
732: }
733: }
734:
735: if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0')
736: return -1;
737:
738: /* build filter */
1.1.1.3 ! misho 739: buffer_copy_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
1.1 misho 740: buffer_append_string_buffer(p->ldap_filter, username);
741: buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post);
742:
743:
744: /* 2. */
745: if (p->anon_conf->ldap == NULL ||
746: 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))) {
747:
748: /* try again; the ldap library sometimes fails for the first call but reconnects */
749: if (p->anon_conf->ldap == NULL || ret != LDAP_SERVER_DOWN ||
750: 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))) {
751:
752: if (auth_ldap_init(srv, p->anon_conf) != HANDLER_GO_ON)
753: return -1;
754:
1.1.1.2 misho 755: if (NULL == p->anon_conf->ldap) return -1;
756:
757: 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))) {
1.1 misho 758: log_error_write(srv, __FILE__, __LINE__, "sssb",
759: "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter);
760: return -1;
761: }
762: }
763: }
764:
765: if (NULL == (first = ldap_first_entry(p->anon_conf->ldap, lm))) {
766: log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
767:
768: ldap_msgfree(lm);
769:
770: return -1;
771: }
772:
773: if (NULL == (dn = ldap_get_dn(p->anon_conf->ldap, first))) {
774: log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
775:
776: ldap_msgfree(lm);
777:
778: return -1;
779: }
780:
781: ldap_msgfree(lm);
782:
783:
784: /* 3. */
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));
787: return -1;
788: }
789:
790: ret = LDAP_VERSION3;
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));
793:
794: ldap_unbind_s(ldap);
795:
796: return -1;
797: }
798:
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));
802:
803: ldap_unbind_s(ldap);
804:
805: return -1;
806: }
807: }
808:
809:
810: if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) {
811: log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
812:
813: ldap_unbind_s(ldap);
814:
815: return -1;
816: }
817:
818: /* 5. */
819: ldap_unbind_s(ldap);
820:
821: /* everything worked, good, access granted */
822:
823: return 0;
824: #endif
1.1.1.3 ! misho 825: } else if (p->conf.auth_backend == AUTH_BACKEND_MYSQL) {
! 826: /*
! 827: we check for md5 crypt() now
! 828: request by Nicola Tiling <nti@w4w.net>
! 829: */
! 830: if (password->ptr[0] == '$' && password->ptr[2] == '$')
! 831: {
! 832: char salt[32];
! 833: char *crypted;
! 834: size_t salt_len = 0;
! 835: char *dollar = NULL;
! 836:
! 837: if (NULL == (dollar = strchr(password->ptr + 3, '$'))) {
! 838: fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
! 839: return -1;
! 840: }
! 841:
! 842: salt_len = dollar - password->ptr;
! 843:
! 844: if (salt_len > sizeof(salt) - 1)
! 845: {
! 846: fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
! 847: return -1;
! 848: }
! 849:
! 850: strncpy(salt, password->ptr, salt_len);
! 851:
! 852: salt[salt_len] = '\0';
! 853:
! 854: crypted = crypt(pw, salt);
! 855:
! 856: if (0 == strcmp(password->ptr, crypted))
! 857: {
! 858: return 0;
! 859: } else {
! 860: fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
! 861: }
! 862: } else
! 863: /* plain md5 check now */
! 864: {
! 865: li_MD5_CTX Md5Ctx;
! 866: HASH HA1;
! 867: char a1[256];
! 868:
! 869: li_MD5_Init(&Md5Ctx);
! 870: li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
! 871: li_MD5_Final(HA1, &Md5Ctx);
! 872:
! 873: CvtHex(HA1, a1);
! 874:
! 875: if (0 == strcmp(password->ptr, a1)) {
! 876: return 0;
! 877: }
! 878: }
1.1 misho 879: }
880: return -1;
881: }
882:
883: int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str) {
884: buffer *username, *password;
885: char *pw;
886:
887: data_string *realm;
888:
889: realm = (data_string *)array_get_element(req, "realm");
1.1.1.3 ! misho 890: if (!realm) return 0; /*(should not happen; config is validated at startup)*/
1.1 misho 891:
892: username = buffer_init();
893:
1.1.1.3 ! misho 894: if (!buffer_append_base64_decode(username, realm_str, strlen(realm_str), BASE64_STANDARD)) {
1.1 misho 895: log_error_write(srv, __FILE__, __LINE__, "sb", "decodeing base64-string failed", username);
896:
897: buffer_free(username);
898: return 0;
899: }
900:
901: /* r2 == user:password */
902: if (NULL == (pw = strchr(username->ptr, ':'))) {
903: log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username);
904:
905: buffer_free(username);
906: return 0;
907: }
908:
1.1.1.3 ! misho 909: buffer_string_set_length(username, pw - username->ptr);
! 910: pw++;
1.1 misho 911:
912: password = buffer_init();
913: /* copy password to r1 */
914: if (http_auth_get_password(srv, p, username, realm->value, password)) {
915: buffer_free(username);
916: buffer_free(password);
917:
918: if (AUTH_BACKEND_UNSET == p->conf.auth_backend) {
919: log_error_write(srv, __FILE__, __LINE__, "s", "auth.backend is not set");
920: } else {
921: log_error_write(srv, __FILE__, __LINE__, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
922: }
923:
924: return 0;
925: }
926:
927: /* password doesn't match */
928: if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) {
929: 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)));
930:
931: buffer_free(username);
932: buffer_free(password);
933:
934: return 0;
935: }
936:
937: /* value is our allow-rules */
938: if (http_auth_match_rules(srv, req, username->ptr, NULL, NULL)) {
939: buffer_free(username);
940: buffer_free(password);
941:
942: log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match");
943:
944: return 0;
945: }
946:
947: /* remember the username */
1.1.1.3 ! misho 948: buffer_copy_buffer(p->auth_user, username);
1.1 misho 949:
950: buffer_free(username);
951: buffer_free(password);
952:
953: return 1;
954: }
955:
956: typedef struct {
957: const char *key;
958: int key_len;
959: char **ptr;
960: } digest_kv;
961:
962: /* return values: -1: error/bad request, 0: failed, 1: success */
963: int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str) {
1.1.1.3 ! misho 964: char a1[33];
! 965: char a2[33];
1.1 misho 966:
967: char *username = NULL;
968: char *realm = NULL;
969: char *nonce = NULL;
970: char *uri = NULL;
971: char *algorithm = NULL;
972: char *qop = NULL;
973: char *cnonce = NULL;
974: char *nc = NULL;
975: char *respons = NULL;
976:
977: char *e, *c;
978: const char *m = NULL;
979: int i;
980: buffer *password, *b, *username_buf, *realm_buf;
981:
982: li_MD5_CTX Md5Ctx;
983: HASH HA1;
984: HASH HA2;
985: HASH RespHash;
986: HASHHEX HA2Hex;
987:
988:
989: /* init pointers */
990: #define S(x) \
991: x, sizeof(x)-1, NULL
992: digest_kv dkv[10] = {
993: { S("username=") },
994: { S("realm=") },
995: { S("nonce=") },
996: { S("uri=") },
997: { S("algorithm=") },
998: { S("qop=") },
999: { S("cnonce=") },
1000: { S("nc=") },
1001: { S("response=") },
1002:
1003: { NULL, 0, NULL }
1004: };
1005: #undef S
1006:
1007: dkv[0].ptr = &username;
1008: dkv[1].ptr = &realm;
1009: dkv[2].ptr = &nonce;
1010: dkv[3].ptr = &uri;
1011: dkv[4].ptr = &algorithm;
1012: dkv[5].ptr = &qop;
1013: dkv[6].ptr = &cnonce;
1014: dkv[7].ptr = &nc;
1015: dkv[8].ptr = &respons;
1016:
1017: UNUSED(req);
1018:
1019: if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST &&
1020: p->conf.auth_backend != AUTH_BACKEND_PLAIN) {
1021: log_error_write(srv, __FILE__, __LINE__, "s",
1022: "digest: unsupported backend (only htdigest or plain)");
1023:
1024: return -1;
1025: }
1026:
1027: b = buffer_init_string(realm_str);
1028:
1029: /* parse credentials from client */
1030: for (c = b->ptr; *c; c++) {
1031: /* skip whitespaces */
1032: while (*c == ' ' || *c == '\t') c++;
1033: if (!*c) break;
1034:
1035: for (i = 0; dkv[i].key; i++) {
1036: if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
1037: if ((c[dkv[i].key_len] == '"') &&
1038: (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
1039: /* value with "..." */
1040: *(dkv[i].ptr) = c + dkv[i].key_len + 1;
1041: c = e;
1042:
1043: *e = '\0';
1044: } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
1045: /* value without "...", terminated by ',' */
1046: *(dkv[i].ptr) = c + dkv[i].key_len;
1047: c = e;
1048:
1049: *e = '\0';
1050: } else {
1051: /* value without "...", terminated by EOL */
1052: *(dkv[i].ptr) = c + dkv[i].key_len;
1053: c += strlen(c) - 1;
1054: }
1.1.1.3 ! misho 1055: break;
1.1 misho 1056: }
1057: }
1058: }
1059:
1060: if (p->conf.auth_debug > 1) {
1061: log_error_write(srv, __FILE__, __LINE__, "ss", "username", username);
1062: log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm);
1063: log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce);
1064: log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri);
1065: log_error_write(srv, __FILE__, __LINE__, "ss", "algorithm", algorithm);
1066: log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop);
1067: log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce);
1068: log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc);
1069: log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons);
1070: }
1071:
1072: /* check if everything is transmitted */
1073: if (!username ||
1074: !realm ||
1075: !nonce ||
1076: !uri ||
1077: (qop && (!nc || !cnonce)) ||
1078: !respons ) {
1079: /* missing field */
1080:
1081: log_error_write(srv, __FILE__, __LINE__, "s",
1082: "digest: missing field");
1083:
1084: buffer_free(b);
1085: return -1;
1086: }
1087:
1088: /**
1089: * protect the md5-sess against missing cnonce and nonce
1090: */
1091: if (algorithm &&
1092: 0 == strcasecmp(algorithm, "md5-sess") &&
1093: (!nonce || !cnonce)) {
1094: log_error_write(srv, __FILE__, __LINE__, "s",
1095: "digest: (md5-sess: missing field");
1096:
1097: buffer_free(b);
1098: return -1;
1099: }
1100:
1101: if (qop && strcasecmp(qop, "auth-int") == 0) {
1102: log_error_write(srv, __FILE__, __LINE__, "s",
1103: "digest: qop=auth-int not supported");
1104:
1105: buffer_free(b);
1106: return -1;
1107: }
1108:
1109: m = get_http_method_name(con->request.http_method);
1.1.1.3 ! misho 1110: force_assert(m);
1.1 misho 1111:
1112: /* password-string == HA1 */
1113: password = buffer_init();
1114: username_buf = buffer_init_string(username);
1115: realm_buf = buffer_init_string(realm);
1116: if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) {
1117: buffer_free(password);
1118: buffer_free(b);
1119: buffer_free(username_buf);
1120: buffer_free(realm_buf);
1121: return 0;
1122: }
1123:
1124: buffer_free(username_buf);
1125: buffer_free(realm_buf);
1126:
1127: if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
1128: /* generate password from plain-text */
1129: li_MD5_Init(&Md5Ctx);
1130: li_MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username));
1.1.1.3 ! misho 1131: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1132: li_MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
1.1.1.3 ! misho 1133: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
! 1134: li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(password));
1.1 misho 1135: li_MD5_Final(HA1, &Md5Ctx);
1136: } else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
1137: /* HA1 */
1138: /* transform the 32-byte-hex-md5 to a 16-byte-md5 */
1139: for (i = 0; i < HASHLEN; i++) {
1140: HA1[i] = hex2int(password->ptr[i*2]) << 4;
1141: HA1[i] |= hex2int(password->ptr[i*2+1]);
1142: }
1143: } else {
1144: /* we already check that above */
1145: SEGFAULT();
1146: }
1147:
1148: buffer_free(password);
1149:
1.1.1.3 ! misho 1150: /* detect if attacker is attempting to reuse valid digest for one uri
! 1151: * on a different request uri. Might also happen if intermediate proxy
! 1152: * altered client request line. (Altered request would not result in
! 1153: * the same digest as that calculated by the client.) */
! 1154: {
! 1155: const size_t ulen = strlen(uri);
! 1156: const size_t rlen = buffer_string_length(con->request.uri);
! 1157: if (!buffer_is_equal_string(con->request.uri, uri, ulen)
! 1158: && !(rlen < ulen && 0 == memcmp(con->request.uri->ptr, uri, rlen) && uri[rlen] == '?')) {
! 1159: log_error_write(srv, __FILE__, __LINE__, "sbssss",
! 1160: "digest: auth failed: uri mismatch (", con->request.uri, "!=", uri, "), IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
! 1161: buffer_free(b);
! 1162: return -1;
! 1163: }
! 1164: }
! 1165:
1.1 misho 1166: if (algorithm &&
1167: strcasecmp(algorithm, "md5-sess") == 0) {
1168: li_MD5_Init(&Md5Ctx);
1169: /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
1.1.1.3 ! misho 1170: CvtHex(HA1, &a1);
! 1171: li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
! 1172: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1173: li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1.1.1.3 ! misho 1174: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1175: li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1176: li_MD5_Final(HA1, &Md5Ctx);
1177: }
1178:
1.1.1.3 ! misho 1179: CvtHex(HA1, &a1);
1.1 misho 1180:
1181: /* calculate H(A2) */
1182: li_MD5_Init(&Md5Ctx);
1183: li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
1.1.1.3 ! misho 1184: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1185: li_MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
1186: /* qop=auth-int not supported, already checked above */
1187: /*
1188: if (qop && strcasecmp(qop, "auth-int") == 0) {
1.1.1.3 ! misho 1189: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1190: li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
1191: }
1192: */
1193: li_MD5_Final(HA2, &Md5Ctx);
1.1.1.3 ! misho 1194: CvtHex(HA2, &HA2Hex);
1.1 misho 1195:
1196: /* calculate response */
1197: li_MD5_Init(&Md5Ctx);
1198: li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
1.1.1.3 ! misho 1199: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1200: li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1.1.1.3 ! misho 1201: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1202: if (qop && *qop) {
1203: li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
1.1.1.3 ! misho 1204: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1205: li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1.1.1.3 ! misho 1206: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1207: li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
1.1.1.3 ! misho 1208: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1 misho 1209: };
1210: li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
1211: li_MD5_Final(RespHash, &Md5Ctx);
1.1.1.3 ! misho 1212: CvtHex(RespHash, &a2);
1.1 misho 1213:
1214: if (0 != strcmp(a2, respons)) {
1215: /* digest not ok */
1216:
1217: if (p->conf.auth_debug) {
1218: log_error_write(srv, __FILE__, __LINE__, "sss",
1219: "digest: digest mismatch", a2, respons);
1220: }
1221:
1222: log_error_write(srv, __FILE__, __LINE__, "ssss",
1223: "digest: auth failed for ", username, ": wrong password, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
1224:
1225: buffer_free(b);
1226: return 0;
1227: }
1228:
1229: /* value is our allow-rules */
1230: if (http_auth_match_rules(srv, req, username, NULL, NULL)) {
1231: buffer_free(b);
1232:
1233: log_error_write(srv, __FILE__, __LINE__, "s",
1234: "digest: rules did match");
1235:
1236: return 0;
1237: }
1238:
1.1.1.3 ! misho 1239: /* check age of nonce. Note that rand() is used in nonce generation
! 1240: * in http_auth_digest_generate_nonce(). If that were replaced
! 1241: * with nanosecond time, then nonce secret would remain unique enough
! 1242: * for the purposes of Digest auth, and would be reproducible (and
! 1243: * verifiable) if nanoseconds were inclued with seconds as part of the
! 1244: * nonce "timestamp:secret". Since that is not done, timestamp in
! 1245: * nonce could theoretically be modified and still produce same md5sum,
! 1246: * but that is highly unlikely within a 10 min (moving) window of valid
! 1247: * time relative to current time (now) */
! 1248: {
! 1249: time_t ts = 0;
! 1250: const unsigned char * const nonce_uns = (unsigned char *)nonce;
! 1251: for (i = 0; i < 8 && light_isxdigit(nonce_uns[i]); ++i) {
! 1252: ts = (ts << 4) + hex2int(nonce_uns[i]);
! 1253: }
! 1254: if (i != 8 || nonce[8] != ':'
! 1255: || ts > srv->cur_ts || srv->cur_ts - ts > 600) { /*(10 mins)*/
! 1256: buffer_free(b);
! 1257: return -2; /* nonce is stale; have client regenerate digest */
! 1258: } /*(future: might send nextnonce when expiration is imminent)*/
! 1259: }
! 1260:
1.1 misho 1261: /* remember the username */
1262: buffer_copy_string(p->auth_user, username);
1263:
1264: buffer_free(b);
1265:
1266: if (p->conf.auth_debug) {
1267: log_error_write(srv, __FILE__, __LINE__, "s",
1268: "digest: auth ok");
1269: }
1270: return 1;
1271: }
1272:
1273:
1.1.1.3 ! misho 1274: int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char (*out)[33]) {
1.1 misho 1275: HASH h;
1276: li_MD5_CTX Md5Ctx;
1.1.1.3 ! misho 1277: char hh[LI_ITOSTRING_LENGTH];
1.1 misho 1278:
1279: UNUSED(p);
1280:
1281: /* generate shared-secret */
1282: li_MD5_Init(&Md5Ctx);
1.1.1.3 ! misho 1283: li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(fn));
! 1284: li_MD5_Update(&Md5Ctx, CONST_STR_LEN("+"));
1.1 misho 1285:
1286: /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1.1.1.3 ! misho 1287: li_itostrn(hh, sizeof(hh), srv->cur_ts);
1.1 misho 1288: li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1289: li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy));
1.1.1.3 ! misho 1290: li_itostrn(hh, sizeof(hh), rand());
1.1 misho 1291: li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1292:
1293: li_MD5_Final(h, &Md5Ctx);
1294:
1295: CvtHex(h, out);
1296:
1297: return 0;
1298: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>