Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2014 L. A. F. Pereira <l@tia.mat.br>
4 : *
5 : * This program is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU General Public License
7 : * as published by the Free Software Foundation; either version 2
8 : * of the License, or any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 : * USA.
19 : */
20 :
21 : #include <errno.h>
22 : #include <stdbool.h>
23 : #include <stdlib.h>
24 : #include <string.h>
25 :
26 : #include "base64.h"
27 : #include "lwan-private.h"
28 : #include "lwan-cache.h"
29 : #include "lwan-config.h"
30 : #include "lwan-http-authorize.h"
31 :
32 : struct realm_password_file_t {
33 : struct cache_entry base;
34 : struct hash *entries;
35 : };
36 :
37 : static struct cache *realm_password_cache = NULL;
38 :
39 0 : static void zero_and_free(void *str)
40 : {
41 0 : if (LIKELY(str)) {
42 0 : lwan_always_bzero(str, strlen(str));
43 0 : free(str);
44 : }
45 0 : }
46 :
47 : static struct cache_entry *
48 3 : create_realm_file(const void *key, void *context __attribute__((unused)))
49 : {
50 3 : struct realm_password_file_t *rpf = malloc(sizeof(*rpf));
51 : const struct config_line *l;
52 : struct config *f;
53 :
54 3 : if (UNLIKELY(!rpf))
55 0 : return NULL;
56 :
57 3 : rpf->entries = hash_str_new(zero_and_free, zero_and_free);
58 3 : if (UNLIKELY(!rpf->entries))
59 0 : goto error_no_close;
60 :
61 : #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
62 : static const uint8_t hardcoded_user_config[] = "user=password\n"
63 : "root=hunter2\n";
64 : f = config_open_for_fuzzing(hardcoded_user_config,
65 : sizeof(hardcoded_user_config));
66 : #else
67 3 : f = config_open(key);
68 : #endif
69 3 : if (!f)
70 0 : goto error_no_close;
71 :
72 9 : while ((l = config_read_line(f))) {
73 : /* FIXME: Storing plain-text passwords in memory isn't a good idea. */
74 6 : switch (l->type) {
75 6 : case CONFIG_LINE_TYPE_LINE: {
76 6 : char *username = strdup(l->key);
77 6 : if (!username)
78 0 : goto error;
79 :
80 6 : char *password = strdup(l->value);
81 6 : if (!password) {
82 0 : free(username);
83 0 : goto error;
84 : }
85 :
86 6 : int err = hash_add_unique(rpf->entries, username, password);
87 6 : if (LIKELY(!err))
88 6 : continue;
89 :
90 0 : zero_and_free(username);
91 0 : zero_and_free(password);
92 :
93 0 : if (err == -EEXIST) {
94 0 : lwan_status_warning(
95 : "Username entry already exists, ignoring: \"%s\"", l->key);
96 0 : continue;
97 : }
98 :
99 0 : goto error;
100 : }
101 0 : default:
102 0 : config_error(f, "Expected username = password");
103 0 : break;
104 : }
105 : }
106 :
107 3 : if (config_last_error(f)) {
108 0 : lwan_status_error("Error on password file \"%s\", line %d: %s", (char *)key,
109 : config_cur_line(f), config_last_error(f));
110 0 : goto error;
111 : }
112 :
113 3 : config_close(f);
114 3 : return (struct cache_entry *)rpf;
115 :
116 0 : error:
117 0 : config_close(f);
118 0 : error_no_close:
119 0 : hash_free(rpf->entries);
120 0 : free(rpf);
121 0 : return NULL;
122 : }
123 :
124 0 : static void destroy_realm_file(struct cache_entry *entry,
125 : void *context __attribute__((unused)))
126 : {
127 0 : struct realm_password_file_t *rpf = (struct realm_password_file_t *)entry;
128 0 : hash_free(rpf->entries);
129 0 : free(rpf);
130 0 : }
131 :
132 92 : bool lwan_http_authorize_init(void)
133 : {
134 92 : realm_password_cache =
135 92 : cache_create(create_realm_file, destroy_realm_file, NULL, 60);
136 :
137 92 : return !!realm_password_cache;
138 : }
139 :
140 0 : void lwan_http_authorize_shutdown(void) { cache_destroy(realm_password_cache); }
141 :
142 3 : static bool authorize(struct coro *coro,
143 : const char *header,
144 : size_t header_len,
145 : const char *password_file)
146 : {
147 : struct realm_password_file_t *rpf;
148 : unsigned char *decoded;
149 : char *colon;
150 : char *password;
151 : char *looked_password;
152 : size_t decoded_len;
153 3 : bool password_ok = false;
154 :
155 3 : rpf = (struct realm_password_file_t *)cache_coro_get_and_ref_entry(
156 : realm_password_cache, coro, password_file);
157 3 : if (UNLIKELY(!rpf))
158 0 : return false;
159 :
160 3 : decoded = base64_decode((unsigned char *)header, header_len, &decoded_len);
161 3 : if (UNLIKELY(!decoded))
162 0 : return false;
163 :
164 3 : colon = memchr(decoded, ':', decoded_len);
165 3 : if (UNLIKELY(!colon))
166 0 : goto out;
167 :
168 3 : *colon = '\0';
169 3 : password = colon + 1;
170 :
171 3 : looked_password = hash_find(rpf->entries, decoded);
172 3 : if (looked_password)
173 2 : password_ok = streq(password, looked_password);
174 :
175 1 : out:
176 3 : free(decoded);
177 3 : return password_ok;
178 : }
179 :
180 4 : bool lwan_http_authorize(struct lwan_request *request,
181 : const char *realm,
182 : const char *password_file)
183 : {
184 : static const char authenticate_tmpl[] = "Basic realm=\"%s\"";
185 : static const size_t basic_len = sizeof("Basic ") - 1;
186 : const char *authorization =
187 4 : lwan_request_get_header(request, "Authorization");
188 :
189 4 : if (LIKELY(authorization && !strncmp(authorization, "Basic ", basic_len))) {
190 3 : const char *header = authorization + basic_len;
191 3 : size_t header_len = strlen(authorization) - basic_len;
192 :
193 3 : if (authorize(request->conn->coro, header, header_len, password_file))
194 1 : return true;
195 : }
196 :
197 6 : const struct lwan_key_value headers[] = {
198 : {"WWW-Authenticate",
199 3 : coro_printf(request->conn->coro, authenticate_tmpl, realm)},
200 : {},
201 : };
202 3 : request->response.headers =
203 3 : coro_memdup(request->conn->coro, headers, sizeof(headers));
204 :
205 3 : return false;
206 : }
|