filemap.c (7758B)
1 /*
2 * Copyright (c) 2019-2022 Joris Vink <joris@coders.se>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <sys/param.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20
21 #include <fcntl.h>
22 #include <dirent.h>
23 #include <unistd.h>
24
25 #include "kore.h"
26
27 #if !defined(KORE_NO_HTTP)
28
29 #include "http.h"
30
31 struct filemap_entry {
32 char *root;
33 size_t root_len;
34 struct kore_domain *domain;
35 char *ondisk;
36 size_t ondisk_len;
37 TAILQ_ENTRY(filemap_entry) list;
38 };
39
40 int filemap_resolve(struct http_request *);
41
42 static void filemap_serve(struct http_request *, const struct filemap_entry *);
43
44 static TAILQ_HEAD(, filemap_entry) maps;
45
46 char *kore_filemap_ext = NULL;
47 char *kore_filemap_index = NULL;
48
49 void
50 kore_filemap_init(void)
51 {
52 TAILQ_INIT(&maps);
53 }
54
55 struct kore_route *
56 kore_filemap_create(struct kore_domain *dom, const char *path,
57 const char *root, const char *auth)
58 {
59 size_t sz;
60 struct stat st;
61 int len;
62 struct kore_route *rt;
63 struct filemap_entry *entry;
64 char regex[1024], fpath[PATH_MAX];
65
66 sz = strlen(root);
67 if (sz == 0)
68 return (NULL);
69
70 if (root[0] != '/' || root[sz - 1] != '/')
71 return (NULL);
72
73 if (worker_privsep.root != NULL) {
74 len = snprintf(fpath, sizeof(fpath), "%s/%s",
75 worker_privsep.root, path);
76 if (len == -1 || (size_t)len >= sizeof(fpath))
77 fatal("kore_filemap_create: failed to concat paths");
78 } else {
79 if (kore_strlcpy(fpath, path, sizeof(fpath)) >= sizeof(fpath))
80 fatal("kore_filemap_create: failed to copy path");
81 }
82
83 if (stat(fpath, &st) == -1) {
84 kore_log(LOG_ERR, "%s: failed to stat '%s': %s", __func__,
85 fpath, errno_s);
86 return (NULL);
87 }
88
89 len = snprintf(regex, sizeof(regex), "^%s.*$", root);
90 if (len == -1 || (size_t)len >= sizeof(regex))
91 fatal("kore_filemap_create: buffer too small");
92
93 if ((rt = kore_route_create(dom, regex, HANDLER_TYPE_DYNAMIC)) == NULL)
94 return (NULL);
95
96 if (auth != NULL) {
97 rt->auth = kore_auth_lookup(auth);
98 if (rt->auth == NULL) {
99 fatal("filemap for '%s' has unknown auth '%s'",
100 path, auth);
101 }
102 }
103
104 kore_route_callback(rt, "filemap_resolve");
105 rt->methods = HTTP_METHOD_GET | HTTP_METHOD_HEAD;
106
107 entry = kore_calloc(1, sizeof(*entry));
108 entry->domain = dom;
109 entry->root_len = sz;
110 entry->root = kore_strdup(root);
111
112 /*
113 * Resolve the ondisk component inside the workers to make sure
114 * realpath() resolves the correct path (they maybe chrooted).
115 */
116 entry->ondisk_len = strlen(path);
117 entry->ondisk = kore_strdup(path);
118
119 TAILQ_INSERT_TAIL(&maps, entry, list);
120
121 return (rt);
122 }
123
124 void
125 kore_filemap_resolve_paths(void)
126 {
127 struct filemap_entry *entry;
128 char rpath[PATH_MAX];
129
130 TAILQ_FOREACH(entry, &maps, list) {
131 if (realpath(entry->ondisk, rpath) == NULL)
132 fatal("realpath(%s): %s", entry->ondisk, errno_s);
133
134 kore_free(entry->ondisk);
135 entry->ondisk_len = strlen(rpath);
136 entry->ondisk = kore_strdup(rpath);
137 }
138 }
139
140 int
141 filemap_resolve(struct http_request *req)
142 {
143 size_t best_len;
144 struct filemap_entry *entry, *best;
145
146 if (req->method != HTTP_METHOD_GET &&
147 req->method != HTTP_METHOD_HEAD) {
148 http_response_header(req, "allow", "get, head");
149 http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
150 return (KORE_RESULT_OK);
151 }
152
153 best = NULL;
154 best_len = 0;
155
156 TAILQ_FOREACH(entry, &maps, list) {
157 if (entry->domain != req->rt->dom)
158 continue;
159
160 if (!strncmp(entry->root, req->path, entry->root_len)) {
161 if (best == NULL || entry->root_len > best_len) {
162 best = entry;
163 best_len = entry->root_len;
164 continue;
165 }
166 }
167 }
168
169 if (best == NULL) {
170 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
171 return (KORE_RESULT_OK);
172 }
173
174 filemap_serve(req, best);
175
176 return (KORE_RESULT_OK);
177 }
178
179 static void
180 filemap_serve(struct http_request *req, const struct filemap_entry *map)
181 {
182 struct stat st;
183 struct connection *c;
184 struct kore_fileref *ref;
185 struct kore_server *srv;
186 const char *path;
187 int len, fd, index;
188 char fpath[PATH_MAX], rpath[PATH_MAX];
189
190 path = req->path + map->root_len;
191
192 len = snprintf(fpath, sizeof(fpath), "%s/%s", map->ondisk, path);
193 if (len == -1 || (size_t)len >= sizeof(fpath)) {
194 http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
195 return;
196 }
197
198 if (!http_argument_urldecode(fpath, 1)) {
199 http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
200 return;
201 }
202
203 index = 0;
204
205 lookup:
206 if (realpath(fpath, rpath) == NULL) {
207 if (errno == ENOENT) {
208 if (index == 0 && kore_filemap_ext != NULL) {
209 len = snprintf(fpath, sizeof(fpath),
210 "%s/%s%s", map->ondisk, path,
211 kore_filemap_ext);
212 if (len == -1 ||
213 (size_t)len >= sizeof(fpath)) {
214 http_response(req,
215 HTTP_STATUS_INTERNAL_ERROR,
216 NULL, 0);
217 return;
218 }
219 index++;
220 goto lookup;
221 }
222 }
223 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
224 return;
225 }
226
227 if (strncmp(rpath, fpath, map->ondisk_len)) {
228 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
229 return;
230 }
231
232 c = req->owner;
233 srv = c->owner->server;
234
235 if ((ref = kore_fileref_get(rpath, srv->tls)) == NULL) {
236 if ((fd = open(fpath, O_RDONLY | O_NOFOLLOW)) == -1) {
237 switch (errno) {
238 case ENOENT:
239 if (index || kore_filemap_ext == NULL) {
240 req->status = HTTP_STATUS_NOT_FOUND;
241 } else {
242 len = snprintf(fpath, sizeof(fpath),
243 "%s/%s%s", map->ondisk, path,
244 kore_filemap_ext);
245 if (len == -1 ||
246 (size_t)len >= sizeof(fpath)) {
247 http_response(req,
248 HTTP_STATUS_INTERNAL_ERROR,
249 NULL, 0);
250 return;
251 }
252 index++;
253 goto lookup;
254 }
255 break;
256 case EPERM:
257 case EACCES:
258 req->status = HTTP_STATUS_FORBIDDEN;
259 break;
260 default:
261 req->status = HTTP_STATUS_INTERNAL_ERROR;
262 break;
263 }
264
265 http_response(req, req->status, NULL, 0);
266 return;
267 }
268
269 if (fstat(fd, &st) == -1) {
270 http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
271 goto cleanup;
272 }
273
274 if (S_ISREG(st.st_mode)) {
275 if (st.st_size <= 0) {
276 http_response(req,
277 HTTP_STATUS_NOT_FOUND, NULL, 0);
278 goto cleanup;
279 }
280
281 /* kore_fileref_create() takes ownership of the fd. */
282 ref = kore_fileref_create(srv, fpath, fd,
283 st.st_size, &st.st_mtim);
284 if (ref == NULL) {
285 http_response(req,
286 HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
287 } else {
288 fd = -1;
289 }
290 } else if (S_ISDIR(st.st_mode) && index == 0) {
291 close(fd);
292 if (req->path[strlen(req->path) - 1] != '/') {
293 (void)snprintf(fpath,
294 sizeof(fpath), "%s/", req->path);
295 http_response_header(req, "location", fpath);
296 http_response(req, HTTP_STATUS_FOUND, NULL, 0);
297 return;
298 }
299
300 len = snprintf(fpath, sizeof(fpath),
301 "%s/%s%s", map->ondisk, path,
302 kore_filemap_index != NULL ?
303 kore_filemap_index : "index.html");
304 if (len == -1 || (size_t)len >= sizeof(fpath)) {
305 http_response(req,
306 HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
307 return;
308 }
309 index++;
310 goto lookup;
311 } else {
312 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
313 }
314 }
315
316 if (ref != NULL) {
317 http_response_fileref(req, HTTP_STATUS_OK, ref);
318 fd = -1;
319 }
320
321 cleanup:
322 if (fd != -1)
323 close(fd);
324 }
325
326 #endif