File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / src / http_auth.c
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Nov 2 10:35:00 2016 UTC (7 years, 7 months ago) by misho
Branches: lighttpd, MAIN
CVS tags: v1_4_41p8, HEAD
lighttpd 1.4.41

    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>