1 /*
2  * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3  * All rights reserved
4  *
5  * "THE BEER-WARE LICENSE" (Revision 42):
6  * Sergey Lyubka wrote this file.  As long as you retain this notice you
7  * can do whatever you want with this stuff. If we meet some day, and you think
8  * this stuff is worth it, you can buy me a beer in return.
9  */
10 
11 #include "defs.h"
12 
13 #if !defined(NO_AUTH)
14 /*
15  * Stringify binary data. Output buffer must be twice as big as input,
16  * because each byte takes 2 bytes in string representation
17  */
18 static void
bin2str(char * to,const unsigned char * p,size_t len)19 bin2str(char *to, const unsigned char *p, size_t len)
20 {
21 	const char	*hex = "0123456789abcdef";
22 
23 	for (;len--; p++) {
24 		*to++ = hex[p[0] >> 4];
25 		*to++ = hex[p[0] & 0x0f];
26 	}
27 }
28 
29 /*
30  * Return stringified MD5 hash for list of vectors.
31  * buf must point to at least 32-bytes long buffer
32  */
33 static void
md5(char * buf,...)34 md5(char *buf, ...)
35 {
36 	unsigned char	hash[16];
37 	const struct vec *v;
38 	va_list		ap;
39 	MD5_CTX	ctx;
40 	int		i;
41 
42 	MD5Init(&ctx);
43 
44 	va_start(ap, buf);
45 	for (i = 0; (v = va_arg(ap, const struct vec *)) != NULL; i++) {
46 		assert(v->len >= 0);
47 		if (v->len == 0)
48 			continue;
49 		if (i > 0)
50 			MD5Update(&ctx, (unsigned char *) ":", 1);
51 		MD5Update(&ctx,(unsigned char *)v->ptr,(unsigned int)v->len);
52 	}
53 	va_end(ap);
54 
55 	MD5Final(hash, &ctx);
56 	bin2str(buf, hash, sizeof(hash));
57 }
58 
59 /*
60  * Compare to vectors. Return 1 if they are equal
61  */
62 static int
vcmp(const struct vec * v1,const struct vec * v2)63 vcmp(const struct vec *v1, const struct vec *v2)
64 {
65 	return (v1->len == v2->len && !memcmp(v1->ptr, v2->ptr, v1->len));
66 }
67 
68 struct digest {
69 	struct vec	user;
70 	struct vec	uri;
71 	struct vec	nonce;
72 	struct vec	cnonce;
73 	struct vec	resp;
74 	struct vec	qop;
75 	struct vec	nc;
76 };
77 
78 static const struct auth_keyword {
79 	size_t		offset;
80 	struct vec	vec;
81 } known_auth_keywords[] = {
82 	{offsetof(struct digest, user),		{"username=",	9}},
83 	{offsetof(struct digest, cnonce),	{"cnonce=",	7}},
84 	{offsetof(struct digest, resp),		{"response=",	9}},
85 	{offsetof(struct digest, uri),		{"uri=",	4}},
86 	{offsetof(struct digest, qop),		{"qop=",	4}},
87 	{offsetof(struct digest, nc),		{"nc=",		3}},
88 	{offsetof(struct digest, nonce),	{"nonce=",	6}},
89 	{0,					{NULL,		0}}
90 };
91 
92 static void
parse_authorization_header(const struct vec * h,struct digest * dig)93 parse_authorization_header(const struct vec *h, struct digest *dig)
94 {
95 	const unsigned char	*p, *e, *s;
96 	struct vec		*v, vec;
97 	const struct auth_keyword *kw;
98 
99 	(void) memset(dig, 0, sizeof(*dig));
100 	p = (unsigned char *) h->ptr + 7;
101 	e = (unsigned char *) h->ptr + h->len;
102 
103 	while (p < e) {
104 
105 		/* Skip spaces */
106 		while (p < e && (*p == ' ' || *p == ','))
107 			p++;
108 
109 		/* Skip to "=" */
110 		for (s = p; s < e && *s != '='; )
111 			s++;
112 		s++;
113 
114 		/* Is it known keyword ? */
115 		for (kw = known_auth_keywords; kw->vec.len > 0; kw++)
116 			if (kw->vec.len <= s - p &&
117 			    !memcmp(p, kw->vec.ptr, kw->vec.len))
118 				break;
119 
120 		if (kw->vec.len == 0)
121 			v = &vec;		/* Dummy placeholder	*/
122 		else
123 			v = (struct vec *) ((char *) dig + kw->offset);
124 
125 		if (*s == '"') {
126 			p = ++s;
127 			while (p < e && *p != '"')
128 				p++;
129 		} else {
130 			p = s;
131 			while (p < e && *p != ' ' && *p != ',')
132 				p++;
133 		}
134 
135 		v->ptr = (char *) s;
136 		v->len = p - s;
137 
138 		if (*p == '"')
139 			p++;
140 
141 		DBG(("auth field [%.*s]", v->len, v->ptr));
142 	}
143 }
144 
145 /*
146  * Check the user's password, return 1 if OK
147  */
148 static int
check_password(int method,const struct vec * ha1,const struct digest * digest)149 check_password(int method, const struct vec *ha1, const struct digest *digest)
150 {
151 	char		a2[32], resp[32];
152 	struct vec	vec_a2;
153 
154 	/* XXX  Due to a bug in MSIE, we do not compare the URI	 */
155 	/* Also, we do not check for authentication timeout */
156 	if (/*strcmp(dig->uri, c->ouri) != 0 || */
157 	    digest->resp.len != 32 /*||
158 	    now - strtoul(dig->nonce, NULL, 10) > 3600 */)
159 		return (0);
160 
161 	md5(a2, &_shttpd_known_http_methods[method], &digest->uri, NULL);
162 	vec_a2.ptr = a2;
163 	vec_a2.len = sizeof(a2);
164 	md5(resp, ha1, &digest->nonce, &digest->nc,
165 	    &digest->cnonce, &digest->qop, &vec_a2, NULL);
166 	DBG(("%s: uri [%.*s] expected_resp [%.*s] resp [%.*s]",
167 	    "check_password", digest->uri.len, digest->uri.ptr,
168 	    32, resp, digest->resp.len, digest->resp.ptr));
169 
170 	return (!memcmp(resp, digest->resp.ptr, 32));
171 }
172 
173 static FILE *
open_auth_file(struct shttpd_ctx * ctx,const char * path)174 open_auth_file(struct shttpd_ctx *ctx, const char *path)
175 {
176 	char 		name[FILENAME_MAX];
177 	const char	*p, *e;
178 	FILE		*fp = NULL;
179 	int		fd;
180 
181 	if (ctx->options[OPT_AUTH_GPASSWD] != NULL) {
182 		/* Use global passwords file */
183 		_shttpd_snprintf(name, sizeof(name), "%s",
184 		    ctx->options[OPT_AUTH_GPASSWD]);
185 	} else {
186 		/*
187 		 * Try to find .htpasswd in requested directory.
188 		 * Given the path, create the path to .htpasswd file
189 		 * in the same directory. Find the right-most
190 		 * directory separator character first. That would be the
191 		 * directory name. If directory separator character is not
192 		 * found, 'e' will point to 'p'.
193 		 */
194 		for (p = path, e = p + strlen(p) - 1; e > p; e--)
195 			if (IS_DIRSEP_CHAR(*e))
196 				break;
197 
198 		/*
199 		 * Make up the path by concatenating directory name and
200 		 * .htpasswd file name.
201 		 */
202 		(void) _shttpd_snprintf(name, sizeof(name), "%.*s/%s",
203 		    (int) (e - p), p, HTPASSWD);
204 	}
205 
206 	if ((fd = _shttpd_open(name, O_RDONLY, 0)) == -1) {
207 		DBG(("open_auth_file: open(%s)", name));
208 	} else if ((fp = fdopen(fd, "r")) == NULL) {
209 		DBG(("open_auth_file: fdopen(%s)", name));
210 		(void) close(fd);
211 	}
212 
213 	return (fp);
214 }
215 
216 /*
217  * Parse the line from htpasswd file. Line should be in form of
218  * "user:domain:ha1". Fill in the vector values. Return 1 if successful.
219  */
220 static int
parse_htpasswd_line(const char * s,struct vec * user,struct vec * domain,struct vec * ha1)221 parse_htpasswd_line(const char *s, struct vec *user,
222 				struct vec *domain, struct vec *ha1)
223 {
224 	user->len = domain->len = ha1->len = 0;
225 
226 	for (user->ptr = s; *s != '\0' && *s != ':'; s++, user->len++);
227 	if (*s++ != ':')
228 		return (0);
229 
230 	for (domain->ptr = s; *s != '\0' && *s != ':'; s++, domain->len++);
231 	if (*s++ != ':')
232 		return (0);
233 
234 	for (ha1->ptr = s; *s != '\0' && !isspace(* (unsigned char *) s);
235 	    s++, ha1->len++);
236 
237 	DBG(("parse_htpasswd_line: [%.*s] [%.*s] [%.*s]", user->len, user->ptr,
238 	    domain->len, domain->ptr, ha1->len, ha1->ptr));
239 
240 	return (user->len > 0 && domain->len > 0 && ha1->len > 0);
241 }
242 
243 /*
244  * Authorize against the opened passwords file. Return 1 if authorized.
245  */
246 static int
authorize(struct conn * c,FILE * fp)247 authorize(struct conn *c, FILE *fp)
248 {
249 	struct vec 	*auth_vec = &c->ch.auth.v_vec;
250 	struct vec	*user_vec = &c->ch.user.v_vec;
251 	struct vec	user, domain, ha1;
252 	struct digest	digest;
253 	int		ok = 0;
254 	char		line[256];
255 
256 	if (auth_vec->len > 20 &&
257 	    !_shttpd_strncasecmp(auth_vec->ptr, "Digest ", 7)) {
258 
259 		parse_authorization_header(auth_vec, &digest);
260 		*user_vec = digest.user;
261 
262 		while (fgets(line, sizeof(line), fp) != NULL) {
263 
264 			if (!parse_htpasswd_line(line, &user, &domain, &ha1))
265 				continue;
266 
267 			DBG(("[%.*s] [%.*s] [%.*s]", user.len, user.ptr,
268 			    domain.len, domain.ptr, ha1.len, ha1.ptr));
269 
270 			if (vcmp(user_vec, &user) &&
271 			    !memcmp(c->ctx->options[OPT_AUTH_REALM],
272 			    domain.ptr, domain.len)) {
273 				ok = check_password(c->method, &ha1, &digest);
274 				break;
275 			}
276 		}
277 	}
278 
279 	return (ok);
280 }
281 
282 int
_shttpd_check_authorization(struct conn * c,const char * path)283 _shttpd_check_authorization(struct conn *c, const char *path)
284 {
285 	FILE		*fp = NULL;
286 	int		len, n, authorized = 1;
287 	const char	*p, *s = c->ctx->options[OPT_PROTECT];
288 	char		protected_path[FILENAME_MAX];
289 
290 	FOR_EACH_WORD_IN_LIST(s, len) {
291 
292 		if ((p = memchr(s, '=', len)) == NULL || p >= s + len || p == s)
293 			continue;
294 
295 		if (!memcmp(c->uri, s, p - s)) {
296 
297 			n = s + len - p;
298 			if (n > (int) sizeof(protected_path) - 1)
299 				n = sizeof(protected_path) - 1;
300 
301 			_shttpd_strlcpy(protected_path, p + 1, n);
302 
303 			if ((fp = fopen(protected_path, "r")) == NULL)
304 				_shttpd_elog(E_LOG, c,
305 				    "check_auth: cannot open %s: %s",
306 				    protected_path, strerror(errno));
307 			break;
308 		}
309 	}
310 
311 	if (fp == NULL)
312 		fp = open_auth_file(c->ctx, path);
313 
314 	if (fp != NULL) {
315 		authorized = authorize(c, fp);
316 		(void) fclose(fp);
317 	}
318 
319 	return (authorized);
320 }
321 
322 int
_shttpd_is_authorized_for_put(struct conn * c)323 _shttpd_is_authorized_for_put(struct conn *c)
324 {
325 	FILE	*fp;
326 	int	ret = 0;
327 
328 	if ((fp = fopen(c->ctx->options[OPT_AUTH_PUT], "r")) != NULL) {
329 		ret = authorize(c, fp);
330 		(void) fclose(fp);
331 	}
332 
333 	return (ret);
334 }
335 
336 void
_shttpd_send_authorization_request(struct conn * c)337 _shttpd_send_authorization_request(struct conn *c)
338 {
339 	char	buf[512];
340 
341 	(void) _shttpd_snprintf(buf, sizeof(buf), "Unauthorized\r\n"
342 	    "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", "
343 	    "nonce=\"%lu\"", c->ctx->options[OPT_AUTH_REALM],
344 	    (unsigned long) _shttpd_current_time);
345 
346 	_shttpd_send_server_error(c, 401, buf);
347 }
348 
349 /*
350  * Edit the passwords file.
351  */
352 int
_shttpd_edit_passwords(const char * fname,const char * domain,const char * user,const char * pass)353 _shttpd_edit_passwords(const char *fname, const char *domain,
354 		const char *user, const char *pass)
355 {
356 	int		ret = EXIT_SUCCESS, found = 0;
357 	struct vec	u, d, p;
358 	char		line[512], tmp[FILENAME_MAX], ha1[32];
359 	FILE		*fp = NULL, *fp2 = NULL;
360 
361 	(void) _shttpd_snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
362 
363 	/* Create the file if does not exist */
364 	if ((fp = fopen(fname, "a+")))
365 		(void) fclose(fp);
366 
367 	/* Open the given file and temporary file */
368 	if ((fp = fopen(fname, "r")) == NULL)
369 		_shttpd_elog(E_FATAL, NULL,
370 		    "Cannot open %s: %s", fname, strerror(errno));
371 	else if ((fp2 = fopen(tmp, "w+")) == NULL)
372 		_shttpd_elog(E_FATAL, NULL,
373 		    "Cannot open %s: %s", tmp, strerror(errno));
374 
375 	p.ptr = pass;
376 	p.len = strlen(pass);
377 
378 	/* Copy the stuff to temporary file */
379 	while (fgets(line, sizeof(line), fp) != NULL) {
380 		u.ptr = line;
381 		if ((d.ptr = strchr(line, ':')) == NULL)
382 			continue;
383 		u.len = d.ptr - u.ptr;
384 		d.ptr++;
385 		if (strchr(d.ptr, ':') == NULL)
386 			continue;
387 		d.len = strchr(d.ptr, ':') - d.ptr;
388 
389 		if ((int) strlen(user) == u.len &&
390 		    !memcmp(user, u.ptr, u.len) &&
391 		    (int) strlen(domain) == d.len &&
392 		    !memcmp(domain, d.ptr, d.len)) {
393 			found++;
394 			md5(ha1, &u, &d, &p, NULL);
395 			(void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
396 		} else {
397 			(void) fprintf(fp2, "%s", line);
398 		}
399 	}
400 
401 	/* If new user, just add it */
402 	if (found == 0) {
403 		u.ptr = user; u.len = strlen(user);
404 		d.ptr = domain; d.len = strlen(domain);
405 		md5(ha1, &u, &d, &p, NULL);
406 		(void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
407 	}
408 
409 	/* Close files */
410 	(void) fclose(fp);
411 	(void) fclose(fp2);
412 
413 	/* Put the temp file in place of real file */
414 	(void) _shttpd_remove(fname);
415 	(void) _shttpd_rename(tmp, fname);
416 
417 	return (ret);
418 }
419 #endif /* NO_AUTH */
420