filemap.c (7606B)
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 *, 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 int
56 kore_filemap_create(struct kore_domain *dom, const char *path, const char *root)
57 {
58 size_t sz;
59 struct stat st;
60 int len;
61 struct kore_route *rt;
62 struct filemap_entry *entry;
63 char regex[1024], fpath[PATH_MAX];
64
65 sz = strlen(root);
66 if (sz == 0)
67 return (KORE_RESULT_ERROR);
68
69 if (root[0] != '/' || root[sz - 1] != '/')
70 return (KORE_RESULT_ERROR);
71
72 if (worker_privsep.root != NULL) {
73 len = snprintf(fpath, sizeof(fpath), "%s/%s",
74 worker_privsep.root, path);
75 if (len == -1 || (size_t)len >= sizeof(fpath))
76 fatal("kore_filemap_create: failed to concat paths");
77 } else {
78 if (kore_strlcpy(fpath, path, sizeof(fpath)) >= sizeof(fpath))
79 fatal("kore_filemap_create: failed to copy path");
80 }
81
82 if (stat(fpath, &st) == -1) {
83 kore_log(LOG_ERR, "%s: failed to stat '%s': %s", __func__,
84 fpath, errno_s);
85 return (KORE_RESULT_ERROR);
86 }
87
88 len = snprintf(regex, sizeof(regex), "^%s.*$", root);
89 if (len == -1 || (size_t)len >= sizeof(regex))
90 fatal("kore_filemap_create: buffer too small");
91
92 if ((rt = kore_route_create(dom, regex, HANDLER_TYPE_DYNAMIC)) == NULL)
93 return (KORE_RESULT_ERROR);
94
95 kore_route_callback(rt, "filemap_resolve");
96 rt->methods = HTTP_METHOD_GET | HTTP_METHOD_HEAD;
97
98 entry = kore_calloc(1, sizeof(*entry));
99 entry->domain = dom;
100 entry->root_len = sz;
101 entry->root = kore_strdup(root);
102
103 /*
104 * Resolve the ondisk component inside the workers to make sure
105 * realpath() resolves the correct path (they maybe chrooted).
106 */
107 entry->ondisk_len = strlen(path);
108 entry->ondisk = kore_strdup(path);
109
110 TAILQ_INSERT_TAIL(&maps, entry, list);
111
112 return (KORE_RESULT_OK);
113 }
114
115 void
116 kore_filemap_resolve_paths(void)
117 {
118 struct filemap_entry *entry;
119 char rpath[PATH_MAX];
120
121 TAILQ_FOREACH(entry, &maps, list) {
122 if (realpath(entry->ondisk, rpath) == NULL)
123 fatal("realpath(%s): %s", entry->ondisk, errno_s);
124
125 kore_free(entry->ondisk);
126 entry->ondisk_len = strlen(rpath);
127 entry->ondisk = kore_strdup(rpath);
128 }
129 }
130
131 int
132 filemap_resolve(struct http_request *req)
133 {
134 size_t best_len;
135 struct filemap_entry *entry, *best;
136
137 if (req->method != HTTP_METHOD_GET &&
138 req->method != HTTP_METHOD_HEAD) {
139 http_response_header(req, "allow", "get, head");
140 http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
141 return (KORE_RESULT_OK);
142 }
143
144 best = NULL;
145 best_len = 0;
146
147 TAILQ_FOREACH(entry, &maps, list) {
148 if (entry->domain != req->rt->dom)
149 continue;
150
151 if (!strncmp(entry->root, req->path, entry->root_len)) {
152 if (best == NULL || entry->root_len > best_len) {
153 best = entry;
154 best_len = entry->root_len;
155 continue;
156 }
157 }
158 }
159
160 if (best == NULL) {
161 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
162 return (KORE_RESULT_OK);
163 }
164
165 filemap_serve(req, best);
166
167 return (KORE_RESULT_OK);
168 }
169
170 static void
171 filemap_serve(struct http_request *req, struct filemap_entry *map)
172 {
173 struct stat st;
174 struct connection *c;
175 struct kore_fileref *ref;
176 struct kore_server *srv;
177 const char *path;
178 int len, fd, index;
179 char fpath[PATH_MAX], rpath[PATH_MAX];
180
181 path = req->path + map->root_len;
182
183 len = snprintf(fpath, sizeof(fpath), "%s/%s", map->ondisk, path);
184 if (len == -1 || (size_t)len >= sizeof(fpath)) {
185 http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
186 return;
187 }
188
189 if (!http_argument_urldecode(fpath)) {
190 http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
191 return;
192 }
193
194 index = 0;
195
196 lookup:
197 if (realpath(fpath, rpath) == NULL) {
198 if (errno == ENOENT) {
199 if (index == 0 && kore_filemap_ext != NULL) {
200 len = snprintf(fpath, sizeof(fpath),
201 "%s/%s%s", map->ondisk, path,
202 kore_filemap_ext);
203 if (len == -1 ||
204 (size_t)len >= sizeof(fpath)) {
205 http_response(req,
206 HTTP_STATUS_INTERNAL_ERROR,
207 NULL, 0);
208 return;
209 }
210 index++;
211 goto lookup;
212 }
213 }
214 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
215 return;
216 }
217
218 if (strncmp(rpath, fpath, map->ondisk_len)) {
219 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
220 return;
221 }
222
223 c = req->owner;
224 srv = c->owner->server;
225
226 if ((ref = kore_fileref_get(rpath, srv->tls)) == NULL) {
227 if ((fd = open(fpath, O_RDONLY | O_NOFOLLOW)) == -1) {
228 switch (errno) {
229 case ENOENT:
230 if (index || kore_filemap_ext == NULL) {
231 req->status = HTTP_STATUS_NOT_FOUND;
232 } else {
233 len = snprintf(fpath, sizeof(fpath),
234 "%s/%s%s", map->ondisk, path,
235 kore_filemap_ext);
236 if (len == -1 ||
237 (size_t)len >= sizeof(fpath)) {
238 http_response(req,
239 HTTP_STATUS_INTERNAL_ERROR,
240 NULL, 0);
241 return;
242 }
243 index++;
244 goto lookup;
245 }
246 break;
247 case EPERM:
248 case EACCES:
249 req->status = HTTP_STATUS_FORBIDDEN;
250 break;
251 default:
252 req->status = HTTP_STATUS_INTERNAL_ERROR;
253 break;
254 }
255
256 http_response(req, req->status, NULL, 0);
257 return;
258 }
259
260 if (fstat(fd, &st) == -1) {
261 http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
262 goto cleanup;
263 }
264
265 if (S_ISREG(st.st_mode)) {
266 if (st.st_size <= 0) {
267 http_response(req,
268 HTTP_STATUS_NOT_FOUND, NULL, 0);
269 goto cleanup;
270 }
271
272 /* kore_fileref_create() takes ownership of the fd. */
273 ref = kore_fileref_create(srv, fpath, fd,
274 st.st_size, &st.st_mtim);
275 if (ref == NULL) {
276 http_response(req,
277 HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
278 } else {
279 fd = -1;
280 }
281 } else if (S_ISDIR(st.st_mode) && index == 0) {
282 close(fd);
283 if (req->path[strlen(req->path) - 1] != '/') {
284 (void)snprintf(fpath,
285 sizeof(fpath), "%s/", req->path);
286 http_response_header(req, "location", fpath);
287 http_response(req, HTTP_STATUS_FOUND, NULL, 0);
288 return;
289 }
290
291 len = snprintf(fpath, sizeof(fpath),
292 "%s/%s%s", map->ondisk, path,
293 kore_filemap_index != NULL ?
294 kore_filemap_index : "index.html");
295 if (len == -1 || (size_t)len >= sizeof(fpath)) {
296 http_response(req,
297 HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
298 return;
299 }
300 index++;
301 goto lookup;
302 } else {
303 http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0);
304 }
305 }
306
307 if (ref != NULL) {
308 http_response_fileref(req, HTTP_STATUS_OK, ref);
309 fd = -1;
310 }
311
312 cleanup:
313 if (fd != -1)
314 close(fd);
315 }
316
317 #endif