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 (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