1: #include "first.h"
2:
3: #include "server.h"
4: #include "log.h"
5: #include "http_auth.h"
6: #include "inet_ntop_cache.h"
7: #include "stream.h"
8: #include "base64.h"
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:
17: #if defined(HAVE_LIBCRYPT) && !defined(HAVE_CRYPT)
18: /* always assume crypt() is present if we have -lcrypt */
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>
33: #include <mysql/mysql.h>
34:
35: #include "md5.h"
36:
37: #ifdef USE_OPENSSL
38: #include <openssl/sha.h>
39: #endif
40:
41: #include "safe_memclear.h"
42:
43: #define HASHLEN 16
44: #define HASHHEXLEN 32
45: typedef unsigned char HASH[HASHLEN];
46: typedef char HASHHEX[HASHHEXLEN+1];
47:
48: static void CvtHex(const HASH Bin, char (*Hex)[33]) {
49: li_tohex(*Hex, sizeof(*Hex), (const char*) Bin, 16);
50: }
51:
52:
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) {
71: if (buffer_is_empty(username) || buffer_is_empty(realm)) return -1;
72:
73: if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
74: FILE *fp;
75: char f_user[1024];
76:
77: if (buffer_string_is_empty(p->conf.auth_htdigest_userfile)) return -1;
78:
79: fp = fopen(p->conf.auth_htdigest_userfile->ptr, "r");
80: if (NULL == fp) {
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:
86: while (NULL != fgets(f_user, sizeof(f_user), fp)) {
87: char *f_pwd, *f_realm;
88: size_t u_len, r_len;
89:
90: /* skip blank lines and comment lines (beginning '#') */
91: if (f_user[0] == '#' || f_user[0] == '\n' || f_user[0] == '\0') continue;
92:
93: /*
94: * htdigest format
95: *
96: * user:realm:md5(user:realm:password)
97: */
98:
99: if (NULL == (f_realm = strchr(f_user, ':'))) {
100: log_error_write(srv, __FILE__, __LINE__, "sbs",
101: "parsed error in", p->conf.auth_htdigest_userfile,
102: "expected 'username:realm:hashed password'");
103:
104: continue; /* skip bad lines */
105: }
106:
107: if (NULL == (f_pwd = strchr(f_realm + 1, ':'))) {
108: log_error_write(srv, __FILE__, __LINE__, "sbs",
109: "parsed error in", p->conf.auth_plain_userfile,
110: "expected 'username:realm:hashed password'");
111:
112: continue; /* skip bad lines */
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:
121: if (buffer_string_length(username) == u_len &&
122: (buffer_string_length(realm) == r_len) &&
123: (0 == strncmp(username->ptr, f_user, u_len)) &&
124: (0 == strncmp(realm->ptr, f_realm, r_len))) {
125: /* found */
126:
127: size_t pwd_len = strlen(f_pwd);
128: if (f_pwd[pwd_len-1] == '\n') --pwd_len;
129:
130: buffer_copy_string_len(password, f_pwd, pwd_len);
131:
132: fclose(fp);
133: return 0;
134: }
135: }
136:
137: fclose(fp);
138: } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
139: p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
140: FILE *fp;
141: char f_user[1024];
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:
146: if (buffer_string_is_empty(auth_fn)) return -1;
147:
148: fp = fopen(auth_fn->ptr, "r");
149: if (NULL == fp) {
150: log_error_write(srv, __FILE__, __LINE__, "sbss",
151: "opening plain-userfile", auth_fn, "failed:", strerror(errno));
152:
153: return -1;
154: }
155:
156: while (NULL != fgets(f_user, sizeof(f_user), fp)) {
157: char *f_pwd;
158: size_t u_len;
159:
160: /* skip blank lines and comment lines (beginning '#') */
161: if (f_user[0] == '#' || f_user[0] == '\n' || f_user[0] == '\0') continue;
162:
163: /*
164: * htpasswd format
165: *
166: * user:crypted passwd
167: */
168:
169: if (NULL == (f_pwd = strchr(f_user, ':'))) {
170: log_error_write(srv, __FILE__, __LINE__, "sbs",
171: "parsed error in", auth_fn,
172: "expected 'username:hashed password'");
173:
174: continue; /* skip bad lines */
175: }
176:
177: /* get pointers to the fields */
178: u_len = f_pwd - f_user;
179: f_pwd++;
180:
181: if (buffer_string_length(username) == u_len &&
182: (0 == strncmp(username->ptr, f_user, u_len))) {
183: /* found */
184:
185: size_t pwd_len = strlen(f_pwd);
186: if (f_pwd[pwd_len-1] == '\n') --pwd_len;
187:
188: buffer_copy_string_len(password, f_pwd, pwd_len);
189:
190: fclose(fp);
191: return 0;
192: }
193: }
194:
195: fclose(fp);
196: } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
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
288:
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: }
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");
322: if (!require) return -1; /*(should not happen; config is validated at startup)*/
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: {
431: static const unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */
432: "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
433:
434: while (--n >= 0) {
435: *s++ = itoa64[v&0x3f];
436: v >>= 6;
437: }
438: }
439:
440: static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
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));
585:
586: /* FIXME
587: */
588: #define apr_cpystrn strncpy
589: apr_cpystrn(result, passwd, nbytes - 1);
590: }
591:
592: #ifdef USE_OPENSSL
593: static void apr_sha_encode(const char *pw, char *result, size_t nbytes) {
594: unsigned char digest[20];
595: size_t base64_written;
596:
597: SHA1((const unsigned char*) pw, strlen(pw), digest);
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:
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 */
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;
631: char a1[33];
632:
633: li_MD5_Init(&Md5Ctx);
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(":"));
638: li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
639: li_MD5_Final(HA1, &Md5Ctx);
640:
641: CvtHex(HA1, &a1);
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 {
660: #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
661: char *crypted;
662: #if defined(HAVE_CRYPT_R)
663: struct crypt_data crypt_tmp_data;
664: crypt_tmp_data.initialized = 0;
665: #endif
666:
667: /* a simple DES password is 2 + 11 characters. everything else should be longer. */
668: if (buffer_string_length(password) < 13) {
669: return -1;
670: }
671:
672: #if defined(HAVE_CRYPT_R)
673: if (0 == (crypted = crypt_r(pw, password->ptr, &crypt_tmp_data))) {
674: #else
675: if (0 == (crypted = crypt(pw, password->ptr))) {
676: #endif
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 };
697: size_t i, len;
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:
716: len = buffer_string_length(username);
717: for (i = 0; i < len; i++) {
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 */
739: buffer_copy_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
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:
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))) {
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
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: }
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");
890: if (!realm) return 0; /*(should not happen; config is validated at startup)*/
891:
892: username = buffer_init();
893:
894: if (!buffer_append_base64_decode(username, realm_str, strlen(realm_str), BASE64_STANDARD)) {
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:
909: buffer_string_set_length(username, pw - username->ptr);
910: pw++;
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 */
948: buffer_copy_buffer(p->auth_user, username);
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) {
964: char a1[33];
965: char a2[33];
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: }
1055: break;
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);
1110: force_assert(m);
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));
1131: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1132: li_MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
1133: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1134: li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(password));
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:
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:
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 */
1170: CvtHex(HA1, &a1);
1171: li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
1172: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1173: li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1174: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1175: li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1176: li_MD5_Final(HA1, &Md5Ctx);
1177: }
1178:
1179: CvtHex(HA1, &a1);
1180:
1181: /* calculate H(A2) */
1182: li_MD5_Init(&Md5Ctx);
1183: li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
1184: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
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) {
1189: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1190: li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
1191: }
1192: */
1193: li_MD5_Final(HA2, &Md5Ctx);
1194: CvtHex(HA2, &HA2Hex);
1195:
1196: /* calculate response */
1197: li_MD5_Init(&Md5Ctx);
1198: li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
1199: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1200: li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1201: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1202: if (qop && *qop) {
1203: li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
1204: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1205: li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1206: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1207: li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
1208: li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1209: };
1210: li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
1211: li_MD5_Final(RespHash, &Md5Ctx);
1212: CvtHex(RespHash, &a2);
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:
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:
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:
1274: int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char (*out)[33]) {
1275: HASH h;
1276: li_MD5_CTX Md5Ctx;
1277: char hh[LI_ITOSTRING_LENGTH];
1278:
1279: UNUSED(p);
1280:
1281: /* generate shared-secret */
1282: li_MD5_Init(&Md5Ctx);
1283: li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(fn));
1284: li_MD5_Update(&Md5Ctx, CONST_STR_LEN("+"));
1285:
1286: /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1287: li_itostrn(hh, sizeof(hh), srv->cur_ts);
1288: li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1289: li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy));
1290: li_itostrn(hh, sizeof(hh), rand());
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>