kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

filemap.c (7755B)



      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)) {
    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