1 #include "server.h"
2 #include "log.h"
3 #include "http_auth.h"
4 #include "inet_ntop_cache.h"
5 #include "stream.h"
6 
7 #ifdef HAVE_CRYPT_H
8 # include <crypt.h>
9 #elif defined(__linux__)
10 /* linux needs _XOPEN_SOURCE */
11 # define _XOPEN_SOURCE
12 #endif
13 
14 #ifdef HAVE_LIBCRYPT
15 # define HAVE_CRYPT
16 #endif
17 
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 
21 #include <fcntl.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <time.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include <ctype.h>
29 
30 #include "md5.h"
31 
32 #define HASHLEN 16
33 #define HASHHEXLEN 32
34 typedef unsigned char HASH[HASHLEN];
35 typedef char HASHHEX[HASHHEXLEN+1];
36 
CvtHex(const HASH Bin,char Hex[33])37 static void CvtHex(const HASH Bin, char Hex[33]) {
38 	unsigned short i;
39 
40 	for (i = 0; i < 16; i++) {
41 		Hex[i*2] = int2hex((Bin[i] >> 4) & 0xf);
42 		Hex[i*2+1] = int2hex(Bin[i] & 0xf);
43 	}
44 	Hex[32] = '\0';
45 }
46 
47 /**
48  * the $apr1$ handling is taken from apache 1.3.x
49  */
50 
51 /*
52  * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
53  * MD5 crypt() function, which is licenced as follows:
54  * ----------------------------------------------------------------------------
55  * "THE BEER-WARE LICENSE" (Revision 42):
56  * <[email protected]> wrote this file.  As long as you retain this notice you
57  * can do whatever you want with this stuff. If we meet some day, and you think
58  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
59  * ----------------------------------------------------------------------------
60  */
61 
62 handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
63 
64 static const char base64_pad = '=';
65 
66 /* "A-Z a-z 0-9 + /" maps to 0-63 */
67 static const short base64_reverse_table[256] = {
68 /*	 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F */
69 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 - 0x0F */
70 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 - 0x1F */
71 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 - 0x2F */
72 	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 - 0x3F */
73 	-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, /* 0x40 - 0x4F */
74 	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 - 0x5F */
75 	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 - 0x6F */
76 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 0x70 - 0x7F */
77 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80 - 0x8F */
78 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90 - 0x9F */
79 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0 - 0xAF */
80 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0 - 0xBF */
81 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0 - 0xCF */
82 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0 - 0xDF */
83 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0 - 0xEF */
84 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xF0 - 0xFF */
85 };
86 
87 
base64_decode(buffer * out,const char * in)88 static unsigned char * base64_decode(buffer *out, const char *in) {
89 	unsigned char *result;
90 	int ch, j = 0, k;
91 	size_t i;
92 
93 	size_t in_len = strlen(in);
94 
95 	buffer_prepare_copy(out, in_len);
96 
97 	result = (unsigned char *)out->ptr;
98 
99 	ch = in[0];
100 	/* run through the whole string, converting as we go */
101 	for (i = 0; i < in_len; i++) {
102 		ch = (unsigned char) in[i];
103 
104 		if (ch == '\0') break;
105 
106 		if (ch == base64_pad) break;
107 
108 		ch = base64_reverse_table[ch];
109 		if (ch < 0) continue;
110 
111 		switch(i % 4) {
112 		case 0:
113 			result[j] = ch << 2;
114 			break;
115 		case 1:
116 			result[j++] |= ch >> 4;
117 			result[j] = (ch & 0x0f) << 4;
118 			break;
119 		case 2:
120 			result[j++] |= ch >>2;
121 			result[j] = (ch & 0x03) << 6;
122 			break;
123 		case 3:
124 			result[j++] |= ch;
125 			break;
126 		}
127 	}
128 	k = j;
129 	/* mop things up if we ended on a boundary */
130 	if (ch == base64_pad) {
131 		switch(i % 4) {
132 		case 0:
133 		case 1:
134 			return NULL;
135 		case 2:
136 			k++;
137 		case 3:
138 			result[k++] = 0;
139 		}
140 	}
141 	result[k] = '\0';
142 
143 	out->used = k;
144 
145 	return result;
146 }
147 
http_auth_get_password(server * srv,mod_auth_plugin_data * p,buffer * username,buffer * realm,buffer * password)148 static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) {
149 	int ret = -1;
150 
151 	if (!username->used|| !realm->used) return -1;
152 
153 	if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
154 		stream f;
155 		char * f_line;
156 
157 		if (buffer_is_empty(p->conf.auth_htdigest_userfile)) return -1;
158 
159 		if (0 != stream_open(&f, p->conf.auth_htdigest_userfile)) {
160 			log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno));
161 
162 			return -1;
163 		}
164 
165 		f_line = f.start;
166 
167 		while (f_line - f.start != f.size) {
168 			char *f_user, *f_pwd, *e, *f_realm;
169 			size_t u_len, pwd_len, r_len;
170 
171 			f_user = f_line;
172 
173 			/*
174 			 * htdigest format
175 			 *
176 			 * user:realm:md5(user:realm:password)
177 			 */
178 
179 			if (NULL == (f_realm = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
180 				log_error_write(srv, __FILE__, __LINE__, "sbs",
181 						"parsed error in", p->conf.auth_htdigest_userfile,
182 						"expected 'username:realm:hashed password'");
183 
184 				stream_close(&f);
185 
186 				return -1;
187 			}
188 
189 			if (NULL == (f_pwd = memchr(f_realm + 1, ':', f.size - (f_realm + 1 - f.start)))) {
190 				log_error_write(srv, __FILE__, __LINE__, "sbs",
191 						"parsed error in", p->conf.auth_plain_userfile,
192 						"expected 'username:realm:hashed password'");
193 
194 				stream_close(&f);
195 
196 				return -1;
197 			}
198 
199 			/* get pointers to the fields */
200 			u_len = f_realm - f_user;
201 			f_realm++;
202 			r_len = f_pwd - f_realm;
203 			f_pwd++;
204 
205 			if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
206 				pwd_len = e - f_pwd;
207 			} else {
208 				pwd_len = f.size - (f_pwd - f.start);
209 			}
210 
211 			if (username->used - 1 == u_len &&
212 			    (realm->used - 1 == r_len) &&
213 			    (0 == strncmp(username->ptr, f_user, u_len)) &&
214 			    (0 == strncmp(realm->ptr, f_realm, r_len))) {
215 				/* found */
216 
217 				buffer_copy_string_len(password, f_pwd, pwd_len);
218 
219 				ret = 0;
220 				break;
221 			}
222 
223 			/* EOL */
224 			if (!e) break;
225 
226 			f_line = e + 1;
227 		}
228 
229 		stream_close(&f);
230 	} else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
231 		   p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
232 		stream f;
233 		char * f_line;
234 		buffer *auth_fn;
235 
236 		auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile;
237 
238 		if (buffer_is_empty(auth_fn)) return -1;
239 
240 		if (0 != stream_open(&f, auth_fn)) {
241 			log_error_write(srv, __FILE__, __LINE__, "sbss",
242 					"opening plain-userfile", auth_fn, "failed:", strerror(errno));
243 
244 			return -1;
245 		}
246 
247 		f_line = f.start;
248 
249 		while (f_line - f.start != f.size) {
250 			char *f_user, *f_pwd, *e;
251 			size_t u_len, pwd_len;
252 
253 			f_user = f_line;
254 
255 			/*
256 			 * htpasswd format
257 			 *
258 			 * user:crypted passwd
259 			 */
260 
261 			if (NULL == (f_pwd = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
262 				log_error_write(srv, __FILE__, __LINE__, "sbs",
263 						"parsed error in", auth_fn,
264 						"expected 'username:hashed password'");
265 
266 				stream_close(&f);
267 
268 				return -1;
269 			}
270 
271 			/* get pointers to the fields */
272 			u_len = f_pwd - f_user;
273 			f_pwd++;
274 
275 			if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
276 				pwd_len = e - f_pwd;
277 			} else {
278 				pwd_len = f.size - (f_pwd - f.start);
279 			}
280 
281 			if (username->used - 1 == u_len &&
282 			    (0 == strncmp(username->ptr, f_user, u_len))) {
283 				/* found */
284 
285 				buffer_copy_string_len(password, f_pwd, pwd_len);
286 
287 				ret = 0;
288 				break;
289 			}
290 
291 			/* EOL */
292 			if (!e) break;
293 
294 			f_line = e + 1;
295 		}
296 
297 		stream_close(&f);
298 	} else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
299 		ret = 0;
300 	} else {
301 		return -1;
302 	}
303 
304 	return ret;
305 }
306 
http_auth_match_rules(server * srv,mod_auth_plugin_data * p,const char * url,const char * username,const char * group,const char * host)307 static int http_auth_match_rules(server *srv, mod_auth_plugin_data *p, const char *url, const char *username, const char *group, const char *host) {
308 	const char *r = NULL, *rules = NULL;
309 	size_t i;
310 	int username_len;
311 	data_string *require;
312 	array *req;
313 
314 	UNUSED(group);
315 	UNUSED(host);
316 
317 	/* check what has to be match to fullfil the request */
318 	/* search auth-directives for path */
319 	for (i = 0; i < p->conf.auth_require->used; i++) {
320 		if (p->conf.auth_require->data[i]->key->used == 0) continue;
321 
322 		if (0 == strncmp(url, p->conf.auth_require->data[i]->key->ptr, p->conf.auth_require->data[i]->key->used - 1)) {
323 			break;
324 		}
325 	}
326 
327 	if (i == p->conf.auth_require->used) {
328 		return -1;
329 	}
330 
331 	req = ((data_array *)(p->conf.auth_require->data[i]))->value;
332 
333 	require = (data_string *)array_get_element(req, "require");
334 
335 	/* if we get here, the user we got a authed user */
336 	if (0 == strcmp(require->value->ptr, "valid-user")) {
337 		return 0;
338 	}
339 
340 	/* user=name1|group=name3|host=name4 */
341 
342 	/* seperate the string by | */
343 #if 0
344 	log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value);
345 #endif
346 
347 	username_len = username ? strlen(username) : 0;
348 
349 	r = rules = require->value->ptr;
350 
351 	while (1) {
352 		const char *eq;
353 		const char *k, *v, *e;
354 		int k_len, v_len, r_len;
355 
356 		e = strchr(r, '|');
357 
358 		if (e) {
359 			r_len = e - r;
360 		} else {
361 			r_len = strlen(rules) - (r - rules);
362 		}
363 
364 		/* from r to r + r_len is a rule */
365 
366 		if (0 == strncmp(r, "valid-user", r_len)) {
367 			log_error_write(srv, __FILE__, __LINE__, "sb",
368 					"parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
369 					require->value);
370 			return -1;
371 		}
372 
373 		/* search for = in the rules */
374 		if (NULL == (eq = strchr(r, '='))) {
375 			log_error_write(srv, __FILE__, __LINE__, "sb",
376 					"parsing the 'require' section in 'auth.require' failed: a = is missing",
377 					require->value);
378 			return -1;
379 		}
380 
381 		/* = out of range */
382 		if (eq > r + r_len) {
383 			log_error_write(srv, __FILE__, __LINE__, "sb",
384 					"parsing the 'require' section in 'auth.require' failed: = out of range",
385 					require->value);
386 
387 			return -1;
388 		}
389 
390 		/* the part before the = is user|group|host */
391 
392 		k = r;
393 		k_len = eq - r;
394 		v = eq + 1;
395 		v_len = r_len - k_len - 1;
396 
397 		if (k_len == 4) {
398 			if (0 == strncmp(k, "user", k_len)) {
399 				if (username &&
400 				    username_len == v_len &&
401 				    0 == strncmp(username, v, v_len)) {
402 					return 0;
403 				}
404 			} else if (0 == strncmp(k, "host", k_len)) {
405 				log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)");
406 			} else {
407 				log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
408 				return -1;
409 			}
410 		} else if (k_len == 5) {
411 			if (0 == strncmp(k, "group", k_len)) {
412 				log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)");
413 			} else {
414 				log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k);
415 				return -1;
416 			}
417 		} else {
418 			log_error_write(srv, __FILE__, __LINE__, "s", "unknown  key");
419 			return -1;
420 		}
421 
422 		if (!e) break;
423 		r = e + 1;
424 	}
425 
426 	log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched");
427 
428 	return -1;
429 }
430 
431 #define APR_MD5_DIGESTSIZE 16
432 #define APR1_ID "$apr1$"
433 
434 /*
435  * The following MD5 password encryption code was largely borrowed from
436  * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is
437  * licenced as stated at the top of this file.
438  */
439 
to64(char * s,unsigned long v,int n)440 static void to64(char *s, unsigned long v, int n)
441 {
442     static const unsigned char itoa64[] =         /* 0 ... 63 => ASCII - 64 */
443         "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
444 
445     while (--n >= 0) {
446         *s++ = itoa64[v&0x3f];
447         v >>= 6;
448     }
449 }
450 
apr_md5_encode(const char * pw,const char * salt,char * result,size_t nbytes)451 static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
452     /*
453      * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
454      * plus 4 for the '$' separators, plus the password hash itself.
455      * Let's leave a goodly amount of leeway.
456      */
457 
458     char passwd[120], *p;
459     const char *sp, *ep;
460     unsigned char final[APR_MD5_DIGESTSIZE];
461     ssize_t sl, pl, i;
462     li_MD5_CTX ctx, ctx1;
463     unsigned long l;
464 
465     /*
466      * Refine the salt first.  It's possible we were given an already-hashed
467      * string as the salt argument, so extract the actual salt value from it
468      * if so.  Otherwise just use the string up to the first '$' as the salt.
469      */
470     sp = salt;
471 
472     /*
473      * If it starts with the magic string, then skip that.
474      */
475     if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) {
476         sp += strlen(APR1_ID);
477     }
478 
479     /*
480      * It stops at the first '$' or 8 chars, whichever comes first
481      */
482     for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
483         continue;
484     }
485 
486     /*
487      * Get the length of the true salt
488      */
489     sl = ep - sp;
490 
491     /*
492      * 'Time to make the doughnuts..'
493      */
494     li_MD5_Init(&ctx);
495 
496     /*
497      * The password first, since that is what is most unknown
498      */
499     li_MD5_Update(&ctx, pw, strlen(pw));
500 
501     /*
502      * Then our magic string
503      */
504     li_MD5_Update(&ctx, APR1_ID, strlen(APR1_ID));
505 
506     /*
507      * Then the raw salt
508      */
509     li_MD5_Update(&ctx, sp, sl);
510 
511     /*
512      * Then just as many characters of the MD5(pw, salt, pw)
513      */
514     li_MD5_Init(&ctx1);
515     li_MD5_Update(&ctx1, pw, strlen(pw));
516     li_MD5_Update(&ctx1, sp, sl);
517     li_MD5_Update(&ctx1, pw, strlen(pw));
518     li_MD5_Final(final, &ctx1);
519     for (pl = strlen(pw); pl > 0; pl -= APR_MD5_DIGESTSIZE) {
520         li_MD5_Update(&ctx, final,
521                       (pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl);
522     }
523 
524     /*
525      * Don't leave anything around in vm they could use.
526      */
527     memset(final, 0, sizeof(final));
528 
529     /*
530      * Then something really weird...
531      */
532     for (i = strlen(pw); i != 0; i >>= 1) {
533         if (i & 1) {
534             li_MD5_Update(&ctx, final, 1);
535         }
536         else {
537             li_MD5_Update(&ctx, pw, 1);
538         }
539     }
540 
541     /*
542      * Now make the output string.  We know our limitations, so we
543      * can use the string routines without bounds checking.
544      */
545     strcpy(passwd, APR1_ID);
546     strncat(passwd, sp, sl);
547     strcat(passwd, "$");
548 
549     li_MD5_Final(final, &ctx);
550 
551     /*
552      * And now, just to make sure things don't run too fast..
553      * On a 60 Mhz Pentium this takes 34 msec, so you would
554      * need 30 seconds to build a 1000 entry dictionary...
555      */
556     for (i = 0; i < 1000; i++) {
557         li_MD5_Init(&ctx1);
558         if (i & 1) {
559             li_MD5_Update(&ctx1, pw, strlen(pw));
560         }
561         else {
562             li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
563         }
564         if (i % 3) {
565             li_MD5_Update(&ctx1, sp, sl);
566         }
567 
568         if (i % 7) {
569             li_MD5_Update(&ctx1, pw, strlen(pw));
570         }
571 
572         if (i & 1) {
573             li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
574         }
575         else {
576             li_MD5_Update(&ctx1, pw, strlen(pw));
577         }
578         li_MD5_Final(final,&ctx1);
579     }
580 
581     p = passwd + strlen(passwd);
582 
583     l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4;
584     l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4;
585     l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4;
586     l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4;
587     l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4;
588     l =                    final[11]                ; to64(p, l, 2); p += 2;
589     *p = '\0';
590 
591     /*
592      * Don't leave anything around in vm they could use.
593      */
594     memset(final, 0, sizeof(final));
595 
596 	/* FIXME
597 	 */
598 #define apr_cpystrn strncpy
599     apr_cpystrn(result, passwd, nbytes - 1);
600 }
601 
602 
603 /**
604  *
605  *
606  * @param password password-string from the auth-backend
607  * @param pw       password-string from the client
608  */
609 
http_auth_basic_password_compare(server * srv,mod_auth_plugin_data * p,array * req,buffer * username,buffer * realm,buffer * password,const char * pw)610 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) {
611 	UNUSED(srv);
612 	UNUSED(req);
613 
614 	if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
615 		/*
616 		 * htdigest format
617 		 *
618 		 * user:realm:md5(user:realm:password)
619 		 */
620 
621 		li_MD5_CTX Md5Ctx;
622 		HASH HA1;
623 		char a1[256];
624 
625 		li_MD5_Init(&Md5Ctx);
626 		li_MD5_Update(&Md5Ctx, (unsigned char *)username->ptr, username->used - 1);
627 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
628 		li_MD5_Update(&Md5Ctx, (unsigned char *)realm->ptr, realm->used - 1);
629 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
630 		li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
631 		li_MD5_Final(HA1, &Md5Ctx);
632 
633 		CvtHex(HA1, a1);
634 
635 		if (0 == strcmp(password->ptr, a1)) {
636 			return 0;
637 		}
638 	} else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
639 		char sample[120];
640 		if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) {
641 			/*
642 			 * The hash was created using $apr1$ custom algorithm.
643 			 */
644 			apr_md5_encode(pw, password->ptr, sample, sizeof(sample));
645 			return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
646 		} else {
647 #ifdef HAVE_CRYPT
648 		char salt[32];
649 		char *crypted;
650 		size_t salt_len = 0;
651 		/*
652 		 * htpasswd format
653 		 *
654 		 * user:crypted password
655 		 */
656 
657 		/*
658 		 *  Algorithm      Salt
659 		 *  CRYPT_STD_DES   2-character (Default)
660 		 *  CRYPT_EXT_DES   9-character
661 		 *  CRYPT_MD5       12-character beginning with $1$
662 		 *  CRYPT_BLOWFISH  16-character beginning with $2$
663 		 */
664 
665 		if (password->used < 13 + 1) {
666 			return -1;
667 		}
668 
669 		if (password->used == 13 + 1) {
670 			/* a simple DES password is 2 + 11 characters */
671 			salt_len = 2;
672 		} else if (password->ptr[0] == '$' && password->ptr[2] == '$') {
673 			char *dollar = NULL;
674 
675 			if (NULL == (dollar = strchr(password->ptr + 3, '$'))) {
676 				return -1;
677 			}
678 
679 			salt_len = dollar - password->ptr;
680 		}
681 
682 		if (salt_len > sizeof(salt) - 1) {
683 			return -1;
684 		}
685 
686 		strncpy(salt, password->ptr, salt_len);
687 
688 		salt[salt_len] = '\0';
689 
690 		crypted = crypt(pw, salt);
691 
692 		if (0 == strcmp(password->ptr, crypted)) {
693 			return 0;
694 		}
695 
696 #endif
697 	}
698 	} else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
699 		if (0 == strcmp(password->ptr, pw)) {
700 			return 0;
701 		}
702 	} else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
703 #ifdef USE_LDAP
704 		LDAP *ldap;
705 		LDAPMessage *lm, *first;
706 		char *dn;
707 		int ret;
708 		char *attrs[] = { LDAP_NO_ATTRS, NULL };
709 		size_t i;
710 
711 		/* for now we stay synchronous */
712 
713 		/*
714 		 * 1. connect anonymously (done in plugin init)
715 		 * 2. get DN for uid = username
716 		 * 3. auth against ldap server
717 		 * 4. (optional) check a field
718 		 * 5. disconnect
719 		 *
720 		 */
721 
722 		/* check username
723 		 *
724 		 * we have to protect us againt username which modifies out filter in
725 		 * a unpleasant way
726 		 */
727 
728 		for (i = 0; i < username->used - 1; i++) {
729 			char c = username->ptr[i];
730 
731 			if (!isalpha(c) &&
732 			    !isdigit(c) &&
733 			    (c != ' ') &&
734 			    (c != '@') &&
735 			    (c != '-') &&
736 			    (c != '_') &&
737 			    (c != '.') ) {
738 
739 				log_error_write(srv, __FILE__, __LINE__, "sbd",
740 					"ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username, i);
741 
742 				return -1;
743 			}
744 		}
745 
746 		if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0')
747 			return -1;
748 
749 		/* build filter */
750 		buffer_copy_string_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
751 		buffer_append_string_buffer(p->ldap_filter, username);
752 		buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post);
753 
754 
755 		/* 2. */
756 		if (p->anon_conf->ldap == NULL ||
757 		    LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
758 
759 			/* try again; the ldap library sometimes fails for the first call but reconnects */
760 			if (p->anon_conf->ldap == NULL || ret != LDAP_SERVER_DOWN ||
761 			    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))) {
762 
763 				if (auth_ldap_init(srv, p->anon_conf) != HANDLER_GO_ON)
764 					return -1;
765 
766 				if (p->anon_conf->ldap == NULL ||
767 				    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))) {
768 					log_error_write(srv, __FILE__, __LINE__, "sssb",
769 							"ldap:", ldap_err2string(ret), "filter:", p->ldap_filter);
770 					return -1;
771 				}
772 			}
773 		}
774 
775 		if (NULL == (first = ldap_first_entry(p->anon_conf->ldap, lm))) {
776 			log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
777 
778 			ldap_msgfree(lm);
779 
780 			return -1;
781 		}
782 
783 		if (NULL == (dn = ldap_get_dn(p->anon_conf->ldap, first))) {
784 			log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
785 
786 			ldap_msgfree(lm);
787 
788 			return -1;
789 		}
790 
791 		ldap_msgfree(lm);
792 
793 
794 		/* 3. */
795 		if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) {
796 			log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
797 			return -1;
798 		}
799 
800 		ret = LDAP_VERSION3;
801 		if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
802 			log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
803 
804 			ldap_unbind_s(ldap);
805 
806 			return -1;
807 		}
808 
809 		if (p->conf.auth_ldap_starttls == 1) {
810 	 		if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(ldap, NULL,  NULL))) {
811 	 			log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret));
812 
813 				ldap_unbind_s(ldap);
814 
815 				return -1;
816 	 		}
817  		}
818 
819 
820 		if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) {
821 			log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
822 
823 			ldap_unbind_s(ldap);
824 
825 			return -1;
826 		}
827 
828 		/* 5. */
829 		ldap_unbind_s(ldap);
830 
831 		/* everything worked, good, access granted */
832 
833 		return 0;
834 #endif
835 	}
836 	return -1;
837 }
838 
http_auth_basic_check(server * srv,connection * con,mod_auth_plugin_data * p,array * req,buffer * url,const char * realm_str)839 int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
840 	buffer *username, *password;
841 	char *pw;
842 
843 	data_string *realm;
844 
845 	realm = (data_string *)array_get_element(req, "realm");
846 
847 	username = buffer_init();
848 
849 	if (!base64_decode(username, realm_str)) {
850 		log_error_write(srv, __FILE__, __LINE__, "sb", "decodeing base64-string failed", username);
851 
852 		buffer_free(username);
853 		return 0;
854 	}
855 
856 	/* r2 == user:password */
857 	if (NULL == (pw = strchr(username->ptr, ':'))) {
858 		log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username);
859 
860 		buffer_free(username);
861 		return 0;
862 	}
863 
864 	*pw++ = '\0';
865 
866 	username->used = pw - username->ptr;
867 
868 	password = buffer_init();
869 	/* copy password to r1 */
870 	if (http_auth_get_password(srv, p, username, realm->value, password)) {
871 		buffer_free(username);
872 		buffer_free(password);
873 
874 		if (AUTH_BACKEND_UNSET == p->conf.auth_backend) {
875 			log_error_write(srv, __FILE__, __LINE__, "s", "auth.backend is not set");
876 		} else {
877 			log_error_write(srv, __FILE__, __LINE__, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
878 		}
879 
880 		return 0;
881 	}
882 
883 	/* password doesn't match */
884 	if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) {
885 		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)));
886 
887 		buffer_free(username);
888 		buffer_free(password);
889 
890 		return 0;
891 	}
892 
893 	/* value is our allow-rules */
894 	if (http_auth_match_rules(srv, p, url->ptr, username->ptr, NULL, NULL)) {
895 		buffer_free(username);
896 		buffer_free(password);
897 
898 		log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match");
899 
900 		return 0;
901 	}
902 
903 	/* remember the username */
904 	buffer_copy_string_buffer(p->auth_user, username);
905 
906 	buffer_free(username);
907 	buffer_free(password);
908 
909 	return 1;
910 }
911 
912 typedef struct {
913 	const char *key;
914 	int key_len;
915 	char **ptr;
916 } digest_kv;
917 
http_auth_digest_check(server * srv,connection * con,mod_auth_plugin_data * p,array * req,buffer * url,const char * realm_str)918 int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
919 	char a1[256];
920 	char a2[256];
921 
922 	char *username = NULL;
923 	char *realm = NULL;
924 	char *nonce = NULL;
925 	char *uri = NULL;
926 	char *algorithm = NULL;
927 	char *qop = NULL;
928 	char *cnonce = NULL;
929 	char *nc = NULL;
930 	char *respons = NULL;
931 
932 	char *e, *c;
933 	const char *m = NULL;
934 	int i;
935 	buffer *password, *b, *username_buf, *realm_buf;
936 
937 	li_MD5_CTX Md5Ctx;
938 	HASH HA1;
939 	HASH HA2;
940 	HASH RespHash;
941 	HASHHEX HA2Hex;
942 
943 
944 	/* init pointers */
945 #define S(x) \
946 	x, sizeof(x)-1, NULL
947 	digest_kv dkv[10] = {
948 		{ S("username=") },
949 		{ S("realm=") },
950 		{ S("nonce=") },
951 		{ S("uri=") },
952 		{ S("algorithm=") },
953 		{ S("qop=") },
954 		{ S("cnonce=") },
955 		{ S("nc=") },
956 		{ S("response=") },
957 
958 		{ NULL, 0, NULL }
959 	};
960 #undef S
961 
962 	dkv[0].ptr = &username;
963 	dkv[1].ptr = &realm;
964 	dkv[2].ptr = &nonce;
965 	dkv[3].ptr = &uri;
966 	dkv[4].ptr = &algorithm;
967 	dkv[5].ptr = &qop;
968 	dkv[6].ptr = &cnonce;
969 	dkv[7].ptr = &nc;
970 	dkv[8].ptr = &respons;
971 
972 	UNUSED(req);
973 
974 	if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST &&
975 	    p->conf.auth_backend != AUTH_BACKEND_PLAIN) {
976 		log_error_write(srv, __FILE__, __LINE__, "s",
977 				"digest: unsupported backend (only htdigest or plain)");
978 
979 		return -1;
980 	}
981 
982 	b = buffer_init_string(realm_str);
983 
984 	/* parse credentials from client */
985 	for (c = b->ptr; *c; c++) {
986 		/* skip whitespaces */
987 		while (*c == ' ' || *c == '\t') c++;
988 		if (!*c) break;
989 
990 		for (i = 0; dkv[i].key; i++) {
991 			if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
992 				if ((c[dkv[i].key_len] == '"') &&
993 				    (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
994 					/* value with "..." */
995 					*(dkv[i].ptr) = c + dkv[i].key_len + 1;
996 					c = e;
997 
998 					*e = '\0';
999 				} else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
1000 					/* value without "...", terminated by ',' */
1001 					*(dkv[i].ptr) = c + dkv[i].key_len;
1002 					c = e;
1003 
1004 					*e = '\0';
1005 				} else {
1006 					/* value without "...", terminated by EOL */
1007 					*(dkv[i].ptr) = c + dkv[i].key_len;
1008 					c += strlen(c) - 1;
1009 				}
1010 			}
1011 		}
1012 	}
1013 
1014 	if (p->conf.auth_debug > 1) {
1015 		log_error_write(srv, __FILE__, __LINE__, "ss", "username", username);
1016 		log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm);
1017 		log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce);
1018 		log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri);
1019 		log_error_write(srv, __FILE__, __LINE__, "ss", "algorithm", algorithm);
1020 		log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop);
1021 		log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce);
1022 		log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc);
1023 		log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons);
1024 	}
1025 
1026 	/* check if everything is transmitted */
1027 	if (!username ||
1028 	    !realm ||
1029 	    !nonce ||
1030 	    !uri ||
1031 	    (qop && (!nc || !cnonce)) ||
1032 	    !respons ) {
1033 		/* missing field */
1034 
1035 		log_error_write(srv, __FILE__, __LINE__, "s",
1036 				"digest: missing field");
1037 
1038 		buffer_free(b);
1039 		return -1;
1040 	}
1041 
1042 	/**
1043 	 * protect the md5-sess against missing cnonce and nonce
1044 	 */
1045 	if (algorithm &&
1046 	    0 == strcasecmp(algorithm, "md5-sess") &&
1047 	    (!nonce || !cnonce)) {
1048 		log_error_write(srv, __FILE__, __LINE__, "s",
1049 				"digest: (md5-sess: missing field");
1050 
1051 		buffer_free(b);
1052 		return -1;
1053 	}
1054 
1055 	m = get_http_method_name(con->request.http_method);
1056 
1057 	/* password-string == HA1 */
1058 	password = buffer_init();
1059 	username_buf = buffer_init_string(username);
1060 	realm_buf = buffer_init_string(realm);
1061 	if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) {
1062 		buffer_free(password);
1063 		buffer_free(b);
1064 		buffer_free(username_buf);
1065 		buffer_free(realm_buf);
1066 		return 0;
1067 	}
1068 
1069 	buffer_free(username_buf);
1070 	buffer_free(realm_buf);
1071 
1072 	if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
1073 		/* generate password from plain-text */
1074 		li_MD5_Init(&Md5Ctx);
1075 		li_MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username));
1076 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1077 		li_MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
1078 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1079 		li_MD5_Update(&Md5Ctx, (unsigned char *)password->ptr, password->used - 1);
1080 		li_MD5_Final(HA1, &Md5Ctx);
1081 	} else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
1082 		/* HA1 */
1083 		/* transform the 32-byte-hex-md5 to a 16-byte-md5 */
1084 		for (i = 0; i < HASHLEN; i++) {
1085 			HA1[i] = hex2int(password->ptr[i*2]) << 4;
1086 			HA1[i] |= hex2int(password->ptr[i*2+1]);
1087 		}
1088 	} else {
1089 		/* we already check that above */
1090 		SEGFAULT();
1091 	}
1092 
1093 	buffer_free(password);
1094 
1095 	if (algorithm &&
1096 	    strcasecmp(algorithm, "md5-sess") == 0) {
1097 		li_MD5_Init(&Md5Ctx);
1098 		/* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
1099 		CvtHex(HA1, a1);
1100 		li_MD5_Update(&Md5Ctx, (unsigned char *)a1, 32);
1101 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1102 		li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1103 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1104 		li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1105 		li_MD5_Final(HA1, &Md5Ctx);
1106 	}
1107 
1108 	CvtHex(HA1, a1);
1109 
1110 	/* calculate H(A2) */
1111 	li_MD5_Init(&Md5Ctx);
1112 	li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
1113 	li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1114 	li_MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
1115 	if (qop && strcasecmp(qop, "auth-int") == 0) {
1116 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1117 		li_MD5_Update(&Md5Ctx, (unsigned char *)"", HASHHEXLEN);
1118 	}
1119 	li_MD5_Final(HA2, &Md5Ctx);
1120 	CvtHex(HA2, HA2Hex);
1121 
1122 	/* calculate response */
1123 	li_MD5_Init(&Md5Ctx);
1124 	li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
1125 	li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1126 	li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1127 	li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1128 	if (qop && *qop) {
1129 		li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
1130 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1131 		li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1132 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1133 		li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
1134 		li_MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
1135 	};
1136 	li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
1137 	li_MD5_Final(RespHash, &Md5Ctx);
1138 	CvtHex(RespHash, a2);
1139 
1140 	if (0 != strcmp(a2, respons)) {
1141 		/* digest not ok */
1142 
1143 		if (p->conf.auth_debug) {
1144 			log_error_write(srv, __FILE__, __LINE__, "sss",
1145 				"digest: digest mismatch", a2, respons);
1146 		}
1147 
1148 		log_error_write(srv, __FILE__, __LINE__, "ssss",
1149 				"digest: auth failed for ", username, ": wrong password, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
1150 
1151 		buffer_free(b);
1152 		return 0;
1153 	}
1154 
1155 	/* value is our allow-rules */
1156 	if (http_auth_match_rules(srv, p, url->ptr, username, NULL, NULL)) {
1157 		buffer_free(b);
1158 
1159 		log_error_write(srv, __FILE__, __LINE__, "s",
1160 				"digest: rules did match");
1161 
1162 		return 0;
1163 	}
1164 
1165 	/* remember the username */
1166 	buffer_copy_string(p->auth_user, username);
1167 
1168 	buffer_free(b);
1169 
1170 	if (p->conf.auth_debug) {
1171 		log_error_write(srv, __FILE__, __LINE__, "s",
1172 				"digest: auth ok");
1173 	}
1174 	return 1;
1175 }
1176 
1177 
http_auth_digest_generate_nonce(server * srv,mod_auth_plugin_data * p,buffer * fn,char out[33])1178 int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char out[33]) {
1179 	HASH h;
1180 	li_MD5_CTX Md5Ctx;
1181 	char hh[32];
1182 
1183 	UNUSED(p);
1184 
1185 	/* generate shared-secret */
1186 	li_MD5_Init(&Md5Ctx);
1187 	li_MD5_Update(&Md5Ctx, (unsigned char *)fn->ptr, fn->used - 1);
1188 	li_MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
1189 
1190 	/* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1191 	LI_ltostr(hh, srv->cur_ts);
1192 	li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1193 	li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy));
1194 	LI_ltostr(hh, rand());
1195 	li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
1196 
1197 	li_MD5_Final(h, &Md5Ctx);
1198 
1199 	CvtHex(h, out);
1200 
1201 	return 0;
1202 }
1203