1/*	$NetBSD: auth-bozo.c,v 1.13 2014/07/08 14:01:21 mrg Exp $	*/
2
3/*	$eterna: auth-bozo.c,v 1.17 2011/11/18 09:21:15 mrg Exp $	*/
4
5/*
6 * Copyright (c) 1997-2014 Matthew R. Green
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer and
16 *    dedication in the documentation and/or other materials provided
17 *    with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 *
31 */
32
33/* this code implements "http basic authorisation" for bozohttpd */
34
35#ifdef DO_HTPASSWD
36
37#include <sys/param.h>
38
39#include <string.h>
40#include <stdlib.h>
41#include <unistd.h>
42
43#include "bozohttpd.h"
44
45#ifndef AUTH_FILE
46#define AUTH_FILE		".htpasswd"
47#endif
48
49static	ssize_t	base64_decode(const unsigned char *, size_t,
50			    unsigned char *, size_t);
51
52/*
53 * Check if HTTP authentication is required
54 */
55int
56bozo_auth_check(bozo_httpreq_t *request, const char *file)
57{
58	bozohttpd_t *httpd = request->hr_httpd;
59	struct stat sb;
60	char dir[MAXPATHLEN], authfile[MAXPATHLEN], *basename;
61	char user[BUFSIZ], *pass;
62	FILE *fp;
63	int len;
64
65			/* get dir=dirname(file) */
66	snprintf(dir, sizeof(dir), "%s", file);
67	if ((basename = strrchr(dir, '/')) == NULL)
68		strcpy(dir, ".");
69	else {
70		*basename++ = '\0';
71			/* ensure basename(file) != AUTH_FILE */
72		if (bozo_check_special_files(request, basename))
73			return 1;
74	}
75	request->hr_authrealm = bozostrdup(httpd, dir);
76
77	if ((size_t)snprintf(authfile, sizeof(authfile), "%s/%s", dir, AUTH_FILE) >=
78	  sizeof(authfile)) {
79		return bozo_http_error(httpd, 404, request,
80			"authfile path too long");
81	}
82	if (stat(authfile, &sb) < 0) {
83		debug((httpd, DEBUG_NORMAL,
84		    "bozo_auth_check realm `%s' dir `%s' authfile `%s' missing",
85		    dir, file, authfile));
86		return 0;
87	}
88	if ((fp = fopen(authfile, "r")) == NULL)
89		return bozo_http_error(httpd, 403, request,
90			"no permission to open authfile");
91	debug((httpd, DEBUG_NORMAL,
92	    "bozo_auth_check realm `%s' dir `%s' authfile `%s' open",
93	    dir, file, authfile));
94	if (request->hr_authuser && request->hr_authpass) {
95		while (fgets(user, sizeof(user), fp) != NULL) {
96			len = strlen(user);
97			if (len > 0 && user[len-1] == '\n')
98				user[--len] = '\0';
99			if ((pass = strchr(user, ':')) == NULL)
100				continue;
101			*pass++ = '\0';
102			debug((httpd, DEBUG_NORMAL,
103			    "bozo_auth_check authfile `%s':`%s' "
104			    	"client `%s':`%s'",
105			    user, pass, request->hr_authuser,
106			    request->hr_authpass));
107			if (strcmp(request->hr_authuser, user) != 0)
108				continue;
109			if (strcmp(crypt(request->hr_authpass, pass),
110					pass) != 0)
111				break;
112			fclose(fp);
113			return 0;
114		}
115	}
116	fclose(fp);
117	return bozo_http_error(httpd, 401, request, "bad auth");
118}
119
120void
121bozo_auth_cleanup(bozo_httpreq_t *request)
122{
123
124	if (request == NULL)
125		return;
126	free(request->hr_authuser);
127	free(request->hr_authpass);
128	free(request->hr_authrealm);
129}
130
131int
132bozo_auth_check_headers(bozo_httpreq_t *request, char *val, char *str, ssize_t len)
133{
134	bozohttpd_t *httpd = request->hr_httpd;
135
136	if (strcasecmp(val, "authorization") == 0 &&
137	    strncasecmp(str, "Basic ", 6) == 0) {
138		char	authbuf[BUFSIZ];
139		char	*pass = NULL;
140		ssize_t	alen;
141
142		alen = base64_decode((unsigned char *)str + 6,
143					(size_t)(len - 6),
144					(unsigned char *)authbuf,
145					sizeof(authbuf) - 1);
146		if (alen != -1)
147			authbuf[alen] = '\0';
148		if (alen == -1 ||
149		    (pass = strchr(authbuf, ':')) == NULL)
150			return bozo_http_error(httpd, 400, request,
151			    "bad authorization field");
152		*pass++ = '\0';
153		request->hr_authuser = bozostrdup(httpd, authbuf);
154		request->hr_authpass = bozostrdup(httpd, pass);
155		debug((httpd, DEBUG_FAT,
156		    "decoded authorization `%s' as `%s':`%s'",
157		    str, request->hr_authuser, request->hr_authpass));
158			/* don't store in request->headers */
159		return 1;
160	}
161	return 0;
162}
163
164int
165bozo_auth_check_special_files(bozo_httpreq_t *request,
166				const char *name)
167{
168	bozohttpd_t *httpd = request->hr_httpd;
169
170	if (strcmp(name, AUTH_FILE) == 0)
171		return bozo_http_error(httpd, 403, request,
172				"no permission to open authfile");
173	return 0;
174}
175
176void
177bozo_auth_check_401(bozo_httpreq_t *request, int code)
178{
179	bozohttpd_t *httpd = request->hr_httpd;
180
181	if (code == 401)
182		bozo_printf(httpd,
183			"WWW-Authenticate: Basic realm=\"%s\"\r\n",
184			(request && request->hr_authrealm) ?
185				request->hr_authrealm : "default realm");
186}
187
188#ifndef NO_CGIBIN_SUPPORT
189void
190bozo_auth_cgi_setenv(bozo_httpreq_t *request,
191			char ***curenvpp)
192{
193	bozohttpd_t *httpd = request->hr_httpd;
194
195	if (request->hr_authuser && *request->hr_authuser) {
196		bozo_setenv(httpd, "AUTH_TYPE", "Basic", (*curenvpp)++);
197		bozo_setenv(httpd, "REMOTE_USER", request->hr_authuser,
198				(*curenvpp)++);
199	}
200}
201
202int
203bozo_auth_cgi_count(bozo_httpreq_t *request)
204{
205	return (request->hr_authuser && *request->hr_authuser) ? 2 : 0;
206}
207#endif /* NO_CGIBIN_SUPPORT */
208
209/*
210 * Decode len bytes starting at in using base64 encoding into out.
211 * Result is *not* NUL terminated.
212 * Written by Luke Mewburn <lukem@NetBSD.org>
213 */
214const unsigned char decodetable[] = {
215	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
216	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
217	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
218	 52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255, 255,   0, 255, 255,
219	255,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
220	 15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
221	255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
222	 41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51, 255, 255, 255, 255, 255,
223};
224
225static ssize_t
226base64_decode(const unsigned char *in, size_t ilen, unsigned char *out,
227	      size_t olen)
228{
229	unsigned char *cp;
230	size_t	 i;
231
232	cp = out;
233	for (i = 0; i < ilen; i += 4) {
234		if (cp + 3 > out + olen)
235			return (-1);
236#define IN_CHECK(x) \
237		if ((x) > sizeof(decodetable) || decodetable[(x)] == 255) \
238			    return(-1)
239
240		IN_CHECK(in[i + 0]);
241		/*LINTED*/
242		*(cp++) = decodetable[in[i + 0]] << 2
243			| decodetable[in[i + 1]] >> 4;
244		IN_CHECK(in[i + 1]);
245		/*LINTED*/
246		*(cp++) = decodetable[in[i + 1]] << 4
247			| decodetable[in[i + 2]] >> 2;
248		IN_CHECK(in[i + 2]);
249		*(cp++) = decodetable[in[i + 2]] << 6
250			| decodetable[in[i + 3]];
251#undef IN_CHECK
252	}
253	while (in[i - 1] == '=')
254		cp--,i--;
255	return (cp - out);
256}
257#endif /* DO_HTPASSWD */
258