1/*	$NetBSD: auth-bozo.c,v 1.28 2023/09/19 07:51:43 shm Exp $	*/
2
3/*	$eterna: auth-bozo.c,v 1.17 2011/11/18 09:21:15 mrg Exp $	*/
4
5/*
6 * Copyright (c) 1997-2021 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
45static	ssize_t	base64_decode(const unsigned char *, size_t,
46			    unsigned char *, size_t);
47
48/*
49 * Check if HTTP authentication is required
50 */
51int
52bozo_auth_check(bozo_httpreq_t *request, const char *file)
53{
54	bozohttpd_t *httpd = request->hr_httpd;
55	struct stat sb;
56	char dir[MAXPATHLEN], authfile[MAXPATHLEN], *basename;
57	char user[BOZO_MINBUFSIZE], *pass;
58	FILE *fp;
59	int len;
60
61			/* get dir=dirname(file) */
62	snprintf(dir, sizeof(dir), "%s", file);
63	if ((basename = strrchr(dir, '/')) == NULL)
64		strcpy(dir, ".");
65	else {
66		*basename++ = '\0';
67		if (bozo_check_special_files(request, basename, true))
68			return 1;
69	}
70
71	/* we might be called from cgi code again with the hr_authrealm
72	 * already set */
73	if (request->hr_authrealm)
74		free(request->hr_authrealm);
75	request->hr_authrealm = bozostrdup(httpd, request, dir);
76
77	if ((size_t)snprintf(authfile, sizeof(authfile), "%s/%s", dir,
78			     AUTH_FILE) >= 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
114#ifndef NO_BLOCKLIST_SUPPORT
115			pfilter_notify(BLOCKLIST_AUTH_OK, 200);
116#endif /* !NO_BLOCKLIST_SUPPORT */
117
118			return 0;
119		}
120	}
121	fclose(fp);
122	return bozo_http_error(httpd, 401, request, "bad auth");
123}
124
125void
126bozo_auth_init(bozo_httpreq_t *request)
127{
128	request->hr_authuser = NULL;
129	request->hr_authpass = NULL;
130	request->hr_authrealm = NULL;
131}
132
133void
134bozo_auth_cleanup(bozo_httpreq_t *request)
135{
136
137	if (request == NULL)
138		return;
139	free(request->hr_authuser);
140	free(request->hr_authpass);
141	free(request->hr_authrealm);
142}
143
144int
145bozo_auth_check_headers(bozo_httpreq_t *request, char *val, char *str,
146			ssize_t len)
147{
148	bozohttpd_t *httpd = request->hr_httpd;
149
150	if (strcasecmp(val, "authorization") == 0 &&
151	    strncasecmp(str, "Basic ", 6) == 0) {
152		char	authbuf[BOZO_MINBUFSIZE];
153		char	*pass = NULL;
154		ssize_t	alen;
155
156		/* free prior entries. */
157		free(request->hr_authuser);
158		free(request->hr_authpass);
159
160		alen = base64_decode((unsigned char *)str + 6,
161					(size_t)(len - 6),
162					(unsigned char *)authbuf,
163					sizeof(authbuf) - 1);
164		if (alen != -1)
165			authbuf[alen] = '\0';
166		if (alen == -1 ||
167		    (pass = strchr(authbuf, ':')) == NULL)
168			return bozo_http_error(httpd, 400, request,
169			    "bad authorization field");
170		*pass++ = '\0';
171		request->hr_authuser = bozostrdup(httpd, request, authbuf);
172		request->hr_authpass = bozostrdup(httpd, request, pass);
173		debug((httpd, DEBUG_FAT,
174		    "decoded authorization `%s' as `%s':`%s'",
175		    str, request->hr_authuser, request->hr_authpass));
176			/* don't store in request->headers */
177		return 1;
178	}
179	return 0;
180}
181
182void
183bozo_auth_check_401(bozo_httpreq_t *request, int code)
184{
185	bozohttpd_t *httpd = request->hr_httpd;
186
187	if (code == 401)
188		bozo_printf(httpd,
189			"WWW-Authenticate: Basic realm=\"%s\"\r\n",
190			request->hr_authrealm ?
191			request->hr_authrealm : "default realm");
192}
193
194#ifndef NO_CGIBIN_SUPPORT
195void
196bozo_auth_cgi_setenv(bozo_httpreq_t *request,
197			char ***curenvpp)
198{
199	bozohttpd_t *httpd = request->hr_httpd;
200
201	if (request->hr_authuser && *request->hr_authuser) {
202		bozo_setenv(httpd, "AUTH_TYPE", "Basic", (*curenvpp)++);
203		bozo_setenv(httpd, "REMOTE_USER", request->hr_authuser,
204				(*curenvpp)++);
205	}
206}
207
208int
209bozo_auth_cgi_count(bozo_httpreq_t *request)
210{
211	return (request->hr_authuser && *request->hr_authuser) ? 2 : 0;
212}
213#endif /* NO_CGIBIN_SUPPORT */
214
215/*
216 * Decode len bytes starting at in using base64 encoding into out.
217 * Result is *not* NUL terminated.
218 * Written by Luke Mewburn <lukem@NetBSD.org>
219 */
220const unsigned char decodetable[] = {
221	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
222	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
223	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
224	 52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255, 255,   0, 255, 255,
225	255,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
226	 15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
227	255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
228	 41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51, 255, 255, 255, 255, 255,
229};
230
231static ssize_t
232base64_decode(const unsigned char *in, size_t ilen, unsigned char *out,
233	      size_t olen)
234{
235	unsigned char *cp;
236	size_t	 i;
237
238	if (ilen == 0) {
239		if (olen)
240			*out = '\0';
241		return 0;
242	}
243
244	cp = out;
245	for (i = 0; i < ilen; i += 4) {
246		if (cp + 3 > out + olen)
247			return (-1);
248#define IN_CHECK(x) \
249		if ((x) > sizeof(decodetable) || decodetable[(x)] == 255) \
250			    return(-1)
251
252		IN_CHECK(in[i + 0]);
253		/*LINTED*/
254		*(cp++) = decodetable[in[i + 0]] << 2
255			| decodetable[in[i + 1]] >> 4;
256		IN_CHECK(in[i + 1]);
257		/*LINTED*/
258		*(cp++) = decodetable[in[i + 1]] << 4
259			| decodetable[in[i + 2]] >> 2;
260		IN_CHECK(in[i + 2]);
261		*(cp++) = decodetable[in[i + 2]] << 6
262			| decodetable[in[i + 3]];
263#undef IN_CHECK
264	}
265	while (i > 0 && in[i - 1] == '=')
266		cp--,i--;
267	return (cp - out);
268}
269#endif /* DO_HTPASSWD */
270