diff options
Diffstat (limited to 'server_file.c')
-rw-r--r-- | server_file.c | 285 |
1 files changed, 194 insertions, 91 deletions
diff --git a/server_file.c b/server_file.c index 0fc3d69..e87bcbd 100644 --- a/server_file.c +++ b/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.17 2014/07/26 22:38:38 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.33 2014/08/14 07:50:35 chrisz Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org> @@ -39,34 +39,43 @@ #include <unistd.h> #include <stdio.h> #include <dirent.h> +#include <time.h> #include <err.h> #include <event.h> -#include <openssl/ssl.h> - #include "httpd.h" #include "http.h" -int server_file_access(struct http_descriptor *, char *, size_t, +int server_file_access(struct httpd *, struct client *, char *, size_t); +int server_file_request(struct httpd *, struct client *, char *, struct stat *); int server_file_index(struct httpd *, struct client *); -void server_file_error(struct bufferevent *, short, void *); +int server_file_method(struct client *); int -server_file_access(struct http_descriptor *desc, char *path, size_t len, - struct stat *st) +server_file_access(struct httpd *env, struct client *clt, + char *path, size_t len) { - struct stat stb; - char *newpath; + struct http_descriptor *desc = clt->clt_desc; + struct server_config *srv_conf = clt->clt_srv_conf; + struct stat st; + char *newpath; + int ret; errno = 0; if (access(path, R_OK) == -1) { goto fail; - } else if (stat(path, st) == -1) { + } else if (stat(path, &st) == -1) { goto fail; - } else if (S_ISDIR(st->st_mode)) { - if (!len) { + } else if (S_ISDIR(st.st_mode)) { + /* Deny access if directory indexing is disabled */ + if (srv_conf->flags & SRVFLAG_NO_INDEX) { + errno = EACCES; + goto fail; + } + + if (desc->http_path_alias != NULL) { /* Recursion - the index "file" is a directory? */ errno = EINVAL; goto fail; @@ -74,39 +83,56 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, /* Redirect to path with trailing "/" */ if (path[strlen(path) - 1] != '/') { - if (asprintf(&newpath, "http://%s%s/", + if (asprintf(&newpath, "http%s://%s%s/", + srv_conf->flags & SRVFLAG_SSL ? "s" : "", desc->http_host, desc->http_path) == -1) return (500); - free(desc->http_path); - desc->http_path = newpath; + /* Path alias will be used for the redirection */ + desc->http_path_alias = newpath; /* Indicate that the file has been moved */ return (301); } - /* Otherwise append the default index file */ - if (strlcat(path, HTTPD_INDEX, len) >= len) { + /* Append the default index file to the location */ + if (asprintf(&newpath, "%s%s", desc->http_path, + srv_conf->index) == -1) + return (500); + desc->http_path_alias = newpath; + if (server_getlocation(clt, newpath) != srv_conf) { + /* The location has changed */ + return (server_file(env, clt)); + } + + /* Otherwise append the default index file to the path */ + if (strlcat(path, srv_conf->index, len) >= len) { errno = EACCES; goto fail; } - /* Check again but set len to 0 to avoid recursion */ - if (server_file_access(desc, path, 0, &stb) == 404) { + ret = server_file_access(env, clt, path, len); + if (ret == 404) { /* - * Index file not found, return success but - * indicate directory with S_ISDIR. + * Index file not found; fail if auto-indexing is + * not enabled, otherwise return success but + * indicate directory with S_ISDIR of the previous + * stat. */ - } else { - /* return updated stat from index file */ - memcpy(st, &stb, sizeof(*st)); + if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { + errno = EACCES; + goto fail; + } + + return (server_file_index(env, clt)); } - } else if (!S_ISREG(st->st_mode)) { + return (ret); + } else if (!S_ISREG(st.st_mode)) { /* Don't follow symlinks and ignore special files */ errno = EACCES; goto fail; } - return (0); + return (server_file_request(env, clt, path, &st)); fail: switch (errno) { @@ -126,72 +152,117 @@ server_file(struct httpd *env, struct client *clt) { struct http_descriptor *desc = clt->clt_desc; struct server_config *srv_conf = clt->clt_srv_conf; - struct media_type *media; - const char *errstr = NULL; - int fd = -1, ret; char path[MAXPATHLEN]; - struct stat st; + const char *errstr = NULL; + int ret = 500; + + if (srv_conf->flags & SRVFLAG_FCGI) + return (server_fcgi(env, clt)); /* Request path is already canonicalized */ if ((size_t)snprintf(path, sizeof(path), "%s%s", - srv_conf->docroot, desc->http_path) >= sizeof(path)) { - /* Do not echo the uncanonicalized path */ - server_abort_http(clt, 500, desc->http_path); - return (-1); + srv_conf->root, + desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path) >= sizeof(path)) { + errstr = desc->http_path; + goto abort; } /* Returns HTTP status code on error */ - if ((ret = server_file_access(desc, path, sizeof(path), &st)) != 0) { - server_abort_http(clt, ret, desc->http_path); - return (-1); + if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { + errstr = desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path; + goto abort; } - if (S_ISDIR(st.st_mode)) { - /* list directory index */ - return (server_file_index(env, clt)); + return (ret); + + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, ret, errstr); + return (-1); +} + +int +server_file_method(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_desc; + + switch (desc->http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + return (0); + default: + /* Other methods are not allowed */ + errno = EACCES; + return (405); + } + /* NOTREACHED */ +} + +int +server_file_request(struct httpd *env, struct client *clt, char *path, + struct stat *st) +{ + struct server_config *srv_conf = clt->clt_srv_conf; + struct media_type *media; + const char *errstr = NULL; + int fd = -1, ret, code = 500; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; } /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) - goto fail; - - /* File descriptor is opened, decrement inflight counter */ - server_inflight_dec(clt, __func__); + goto abort; media = media_find(env->sc_mediatypes, path); - ret = server_response_http(clt, 200, media, st.st_size); + ret = server_response_http(clt, 200, media, st->st_size); switch (ret) { case -1: goto fail; case 0: /* Connection is already finished */ close(fd); - return (0); + goto done; default: break; } clt->clt_fd = fd; - if (clt->clt_file != NULL) - bufferevent_free(clt->clt_file); + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); - clt->clt_file = bufferevent_new(clt->clt_fd, server_read, + clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, server_write, server_file_error, clt); - if (clt->clt_file == NULL) { + if (clt->clt_srvbev == NULL) { errstr = "failed to allocate file buffer event"; goto fail; } - bufferevent_settimeout(clt->clt_file, + /* Adjust read watermark to the socket output buffer size */ + bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, + clt->clt_sndbufsiz); + + bufferevent_settimeout(clt->clt_srvbev, srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); - bufferevent_enable(clt->clt_file, EV_READ); + bufferevent_enable(clt->clt_srvbev, EV_READ); bufferevent_disable(clt->clt_bev, EV_READ); + done: + server_reset_http(clt); return (0); fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: if (errstr == NULL) errstr = strerror(errno); - server_abort_http(clt, 500, errstr); + server_abort_http(clt, code, errstr); return (-1); } @@ -199,38 +270,47 @@ int server_file_index(struct httpd *env, struct client *clt) { char path[MAXPATHLEN]; + char tmstr[21]; struct http_descriptor *desc = clt->clt_desc; struct server_config *srv_conf = clt->clt_srv_conf; struct dirent **namelist, *dp; - int namesize, i, ret, fd = -1, width; + int namesize, i, ret, fd = -1, namewidth, skip; + int code = 500; struct evbuffer *evb = NULL; struct media_type *media; const char *style; struct stat st; + struct tm tm; + time_t t; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; + } /* Request path is already canonicalized */ if ((size_t)snprintf(path, sizeof(path), "%s%s", - srv_conf->docroot, desc->http_path) >= sizeof(path)) - goto fail; + srv_conf->root, desc->http_path) >= sizeof(path)) + goto abort; /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) - goto fail; - - /* File descriptor is opened, decrement inflight counter */ - server_inflight_dec(clt, __func__); + goto abort; if ((evb = evbuffer_new()) == NULL) - goto fail; + goto abort; if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) - goto fail; + goto abort; + + /* Indicate failure but continue going through the list */ + skip = 0; /* A CSS stylesheet allows minimal customization by the user */ style = "body { background-color: white; color: black; font-family: " "sans-serif; }"; /* Generate simple HTML index document */ - evbuffer_add_printf(evb, + if (evbuffer_add_printf(evb, "<!DOCTYPE HTML PUBLIC " "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" "<html>\n" @@ -241,35 +321,51 @@ server_file_index(struct httpd *env, struct client *clt) "<body>\n" "<h1>Index of %s</h1>\n" "<hr>\n<pre>\n", - desc->http_path, style, desc->http_path); + desc->http_path, style, desc->http_path) == -1) + skip = 1; for (i = 0; i < namesize; i++) { dp = namelist[i]; - if (dp->d_name[0] == '.' && dp->d_name[1] == '\0') { - /* ignore */ - } else if (dp->d_type == DT_DIR) { - evbuffer_add_printf(evb, - "<a href=\"%s/\">%s/</a>\n", - dp->d_name, dp->d_name); - } else if (dp->d_type == DT_REG) { - if (fstatat(fd, dp->d_name, &st, 0) == -1) { - free(dp); - continue; - } - width = 50 - strlen(dp->d_name); - evbuffer_add_printf(evb, - "<a href=\"%s\">%s</a>%*s%llu bytes\n", + + if (skip || + fstatat(fd, dp->d_name, &st, 0) == -1) { + free(dp); + continue; + } + + t = st.st_mtime; + localtime_r(&t, &tm); + strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm); + namewidth = 51 - strlen(dp->d_name); + + if (dp->d_name[0] == '.' && + !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) { + /* ignore hidden files starting with a dot */ + } else if (S_ISDIR(st.st_mode)) { + namewidth -= 1; /* trailing slash */ + if (evbuffer_add_printf(evb, + "<a href=\"%s\">%s/</a>%*s%s%20s\n", + dp->d_name, dp->d_name, + MAX(namewidth, 0), " ", tmstr, "-") == -1) + skip = 1; + } else if (S_ISREG(st.st_mode)) { + if (evbuffer_add_printf(evb, + "<a href=\"%s\">%s</a>%*s%s%20llu\n", dp->d_name, dp->d_name, - width < 0 ? 50 : width, "", st.st_size); + MAX(namewidth, 0), " ", tmstr, st.st_size) == -1) + skip = 1; } free(dp); } free(namelist); - evbuffer_add_printf(evb, - "</pre>\n<hr>\n</body>\n</html>\n"); + if (skip || + evbuffer_add_printf(evb, + "</pre>\n<hr>\n</body>\n</html>\n") == -1) + goto abort; close(fd); + fd = -1; media = media_find(env->sc_mediatypes, "index.html"); ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb)); @@ -279,7 +375,7 @@ server_file_index(struct httpd *env, struct client *clt) case 0: /* Connection is already finished */ evbuffer_free(evb); - return (0); + goto done; default: break; } @@ -287,6 +383,7 @@ server_file_index(struct httpd *env, struct client *clt) if (server_bufferevent_write_buffer(clt, evb) == -1) goto fail; evbuffer_free(evb); + evb = NULL; bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); if (clt->clt_persist) @@ -295,14 +392,19 @@ server_file_index(struct httpd *env, struct client *clt) clt->clt_toread = TOREAD_HTTP_NONE; clt->clt_done = 0; + done: + server_reset_http(clt); return (0); - fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: if (fd != -1) close(fd); if (evb != NULL) evbuffer_free(evb); - server_abort_http(clt, 500, desc->http_path); + server_abort_http(clt, code, desc->http_path); return (-1); } @@ -321,13 +423,6 @@ server_file_error(struct bufferevent *bev, short error, void *arg) clt->clt_done = 1; - dst = EVBUFFER_OUTPUT(clt->clt_bev); - if (EVBUFFER_LENGTH(dst)) { - /* Finish writing all data first */ - bufferevent_enable(clt->clt_bev, EV_WRITE); - return; - } - if (clt->clt_persist) { /* Close input file and wait for next HTTP request */ if (clt->clt_fd != -1) @@ -338,6 +433,14 @@ server_file_error(struct bufferevent *bev, short error, void *arg) bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); return; } + + dst = EVBUFFER_OUTPUT(clt->clt_bev); + if (EVBUFFER_LENGTH(dst)) { + /* Finish writing all data first */ + bufferevent_enable(clt->clt_bev, EV_WRITE); + return; + } + server_close(clt, "done"); return; } |