Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,2 +1,4 @@ filed filed.o +filed-mime-types.h +compiled Index: LICENSE ================================================================== --- LICENSE +++ LICENSE @@ -1,6 +1,6 @@ -Copyright (c) 2014, Roy Keene +Copyright (c) 2014 - 2016, Roy Keene All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -1,31 +1,34 @@ CC = gcc -CFLAGS = -Wall -Werror -W -pthread -O3 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -LDFLAGS = -pthread -LIBS = -lpthread +CFLAGS = -I. -Wall -W -pthread -O3 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE $(FILED_EXTRA_CFLAGS) +LDFLAGS = -pthread $(FILED_EXTRA_LDFLAGS) +LIBS = -lpthread $(FILED_EXTRA_LIBS) MIMETYPES = /etc/httpd/mime.types PREFIX = /usr/local prefix = $(PREFIX) bindir = $(prefix)/bin mandir = $(prefix)/share/man +srcdir = . +vpath %.c $(srcdir) all: filed filed: filed.o $(CC) $(CFLAGS) $(LDFLAGS) -o "$@" $^ $(LIBS) -filed.o: filed.c filed-mime-types.h +filed.o: $(srcdir)/filed.c filed-mime-types.h -filed-mime-types.h: generate-mime-types - ./generate-mime-types "$(MIMETYPES)" > filed-mime-types.h.new +filed-mime-types.h: $(srcdir)/generate-mime-types $(srcdir)/mime.types + '$(srcdir)/generate-mime-types' '$(MIMETYPES)' > filed-mime-types.h.new || \ + '$(srcdir)/generate-mime-types' '$(srcdir)/mime.types' > filed-mime-types.h.new mv filed-mime-types.h.new filed-mime-types.h -install: filed filed.1 +install: filed $(srcdir)/filed.1 test -d "$(DESTDIR)$(mandir)/man1" || mkdir -p "$(DESTDIR)$(mandir)/man1" test -d "$(DESTDIR)$(bindir)" || mkdir -p "$(DESTDIR)$(bindir)" - cp filed.1 "$(DESTDIR)$(mandir)/man1/" + cp '$(srcdir)/filed.1' "$(DESTDIR)$(mandir)/man1/" cp filed "$(DESTDIR)$(bindir)/" clean: rm -f filed filed.o rm -f filed-mime-types.h.new Index: README ================================================================== --- README +++ README @@ -61,14 +61,28 @@ 3. Differing HTTP semantics (CFLAGS, -DFILED_NONBLOCK_HTTP=1) It is possible that some HTTP clients may not process the HTTP stream being delivered if they cannot write to the HTTP stream itself. This has not been observed yet, but it is possible. If these semantics are needed (and they should not be) then they can be enabled with this - flag at the cost of performance + flag at the cost of performance. + + 4. Differing chroot() semantics (CFLAGS, -DFILED_FAKE_CHROOT=1) + In some cases it is desirable to mangle paths with a path prefix + rather than call chroot() at startup. This is less secure and slower + and should be generally avoided -- however it may be necessary to do. + In these cases the executable may be compiled with the + FILED_FAKE_CHROOT C preprocessor macro defined and instead of calling + chroot() all HTTP requests will have the root suffix specified as the + argument to the "-r" or "--root" option prepended to them. + + 5. Differing "index.html" handling (CFLAGS, -DFILED_DONT_REDIRECT_DIRECTORIES=1) + Normally "filed" redirects users who request a directory to the + index.html file in that directory so that no memory allocations are + required; This option lets the server generate the new path. - 4. MIME Types (MIMETYPES) - For single-file convience "filed" compiles the mapping of file + 6. MIME Types (MIMETYPES) + For single-file convenience "filed" compiles the mapping of file extensions (the string in the filename following its last dot (".")) into the executable. This mapping comes from a file in the format of type1 type1_extension1 type1_extension2... type2 type2_extension1 type2_extension2... ... @@ -78,11 +92,11 @@ mapping is desired, "/dev/null" may be specified. Log Files --------- Because "filed" relies on chroot(2) and setuid(2), log files cannot reliably -be re-opened. If you need log rotation a second process, which can close and -re-open log files, must be used. Any process may be used for writing logs to -but if the process does not support log rotation it will not provide that +be re-opened. If you need log rotation then a second process, which can close +and re-open log files, must be used. Any process may be used for writing logs +but if the process does not support log rotation then it will not provide that benefit. For example, if you wish to write logs to syslogd(8) you can use logger(1), such as: # ./filed --root /www --user nobody --log '|logger -t filed' --daemon ADDED build/build-precompiled Index: build/build-precompiled ================================================================== --- /dev/null +++ build/build-precompiled @@ -0,0 +1,65 @@ +#! /usr/bin/env bash + +# Ensure we are in the correct working directory +cd "$(dirname "$(which "$0")")/.." || exit 1 + +# Determine the version of Filed +version='' +eval "$(( grep '^# *define *FILED_VERSION' filed.c; echo '|version=FILED_VERSION' ) | cpp -E | grep '^|' | sed 's@^|@@')" +if [ -z "${version}" ]; then + echo "Unable to determine which version of Filed we are compiling. Aborting." >&2 + + exit 1 +fi + +# Cleanup +rm -rf workdir-buildPrecompiled-* + +# Compile everything, all at once +idx=-1 +for tryCompilerDir in "$(readlink -f ~/root/cross-compilers)" "$(readlink -f ~/devel/build-cc/TMP)"; do + setup_cc="${tryCompilerDir}/setup-cc" + + platforms=( + $("${setup_cc}" | tail -n +2) + ) + + for platform in "${platforms[@]}"; do + idx=$[$idx + 1] + ( + workdir="workdir-buildPrecompiled-${idx}-$(openssl rand 20 -hex)-platform-${platform}" || exit 1 + mkdir "${workdir}" || exit 1 + cd "${workdir}" || exit 1 + + eval $("${setup_cc}" "${platform}") + make_extra=( + -f ../Makefile + srcdir=.. + CC="${CC}" + ) + + case "${platform}" in + *-musl-*|*-musl) + make_extra=("${make_extra[@]}" FILED_EXTRA_LDFLAGS="-static") + ;; + esac + + make "${make_extra[@]}" + ) & + done +done + +# Wait for that to get done +wait + +# Rename the files into place +mkdir -p compiled +for binary in workdir-buildPrecompiled-*/filed; do + platform="$(echo "${binary}" | sed 's@^.*-platform-@@;s@/.*$@@')" + mv "${binary}" "compiled/filed-${version}-${platform}" +done + +# Cleanup +rm -rf workdir-buildPrecompiled-* + +exit 0 Index: build/update-wiki ================================================================== --- build/update-wiki +++ build/update-wiki @@ -1,9 +1,9 @@ #! /bin/bash ( echo '

NAME

' man2html -H linux.die.net -M /man -p filed.1 | \ - sed '0,/

NAME<\/H2>/ d;/

Index<\/H2>/,$ d;s@Return to Main Contents@@;s@\[@\[@;s@\]@\]@' | \ + sed '0,/

NAME<\/H2>/ d;/

Index<\/H2>/,$ d;s@Return to Main Contents@@;s@\[@\[@g;s@\]@\]@g' | \ sed '$ d;/^ *$/ d' | \ sed 's@\(http://linux.die.net/man/[^+]*\)+@\1/@' ) | fossil wiki commit Manual Index: filed.1 ================================================================== --- filed.1 +++ filed.1 @@ -1,7 +1,7 @@ .PU -.TH FILED 1 "19 Feb 14" "filed 1.9" +.TH FILED 1 "22 Sep 2016" "filed 1.21" .SH NAME filed \- serve files over HTTP .SH SYNOPSIS .ll +10 .B filed Index: filed.c ================================================================== --- filed.c +++ filed.c @@ -1,5 +1,29 @@ +/* + * Copyright (c) 2014 - 2016, Roy Keene + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ #include #include #include #include #include @@ -19,11 +43,11 @@ #include #include #include /* Compile time constants */ -#define FILED_VERSION "1.9" +#define FILED_VERSION "1.21" #define FILED_SENDFILE_MAX 16777215 #define FILED_MAX_FAILURE_COUNT 30 #define FILED_DEFAULT_TYPE "application/octet-stream" #define FILED_PATH_BUFFER_SIZE 1010 @@ -31,14 +55,48 @@ #define PORT 80 #define THREAD_COUNT 5 #define BIND_ADDR "::" #define CACHE_SIZE 8209 #define LOG_FILE "-" + +/* Fuzzing Test Code */ +#ifdef FILED_TEST_AFL +#define FILED_DONT_LOG 1 +#define pthread_create(a, x, y, z) afl_pthread_create(a, x, y, z) +#define bind(x, y, z) afl_bind(x, y, z) +#define socket(x, y, z) 8193 +#define listen(x, y) 0 +#define accept(x, y, z) afl_accept(x, y, z) +#define close(x) { if (strcmp(#x, "random_fd") == 0) { close(x); } else { exit(0); } } +#define fclose(x) exit(0) + +static int afl_accept(int x, void *addr, void *z) { + ((struct sockaddr_in6 *) addr)->sin6_family = AF_INET + AF_INET6 + 1; + return(STDIN_FILENO); + x = x; + z = z; +} + +static int afl_bind(int x, void *y, socklen_t z) { + return(8194); + x = x; + y = y; + z = z; +} + +static int afl_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) { + start_routine(arg); + exit(3); + thread = thread; + attr = attr; +} +#endif /* Configuration options that work threads need to be aware of */ struct filed_options { int vhosts_enabled; + const char *fake_newroot; }; /* Arguments for worker threads */ struct filed_worker_thread_args { int fd; @@ -93,10 +151,15 @@ struct { int present; char host[FILED_PATH_BUFFER_SIZE]; } host; + + enum { + FILED_CONNECTION_CLOSE, + FILED_CONNECTION_KEEP_ALIVE + } connection; } headers; }; /* Log record */ struct filed_log_entry { @@ -118,10 +181,11 @@ char buffer[FILED_PATH_BUFFER_SIZE]; /* Items for type = TRANSFER */ int http_code; const char *reason; + time_t connecttime; time_t starttime; time_t endtime; off_t req_offset; off_t req_length; off_t sent_length; @@ -138,10 +202,40 @@ /** Logging **/ struct filed_log_entry *filed_log_msg_list; pthread_mutex_t filed_log_msg_list_mutex; pthread_cond_t filed_log_msg_list_ready; + +/* Signal Handler */ +static void filed_signal_handler(int signal_number) { + struct filed_fileinfo *cache; + unsigned int idx; + + switch (signal_number) { + case SIGHUP: + for (idx = 0; idx < filed_fileinfo_fdcache_size; idx++) { + cache = &filed_fileinfo_fdcache[idx]; + + pthread_mutex_lock(&cache->mutex); + + cache->path[0] = '\0'; + if (cache->fd >= 0) { + close(cache->fd); + + cache->fd = -1; + } + + cache->lastmod = ""; + cache->type = ""; + + pthread_mutex_unlock(&cache->mutex); + } + break; + } + + return; +} /* Initialize cache */ static int filed_init_cache(unsigned int cache_size) { unsigned int idx; int mutex_init_ret; @@ -148,10 +242,15 @@ /* Cache may not be re-initialized */ if (filed_fileinfo_fdcache_size != 0 || filed_fileinfo_fdcache != NULL) { return(1); } + + /* Cache does not need to be allocated if cache is not enabled */ + if (cache_size == 0) { + return(0); + } /* Allocate cache */ filed_fileinfo_fdcache_size = cache_size; filed_fileinfo_fdcache = malloc(sizeof(*filed_fileinfo_fdcache) * filed_fileinfo_fdcache_size); if (filed_fileinfo_fdcache == NULL) { @@ -175,10 +274,13 @@ } /* Initialize process */ static int filed_init(unsigned int cache_size) { static int called = 0; + struct sigaction signal_handler_info; + sigset_t signal_handler_mask; + ssize_t read_ret = 0; unsigned int random_value = 0; int cache_ret; int random_fd; if (called) { @@ -188,12 +290,24 @@ called = 1; /* Attempt to lock all memory to physical RAM (but don't care if we can't) */ mlockall(MCL_CURRENT | MCL_FUTURE); - /* Ignore SIGPIPE */ - signal(SIGPIPE, SIG_IGN); + /* Establish signal handlers */ + /* SIGPIPE should interrupt system calls */ + sigfillset(&signal_handler_mask); + signal_handler_info.sa_handler = filed_signal_handler; + signal_handler_info.sa_mask = signal_handler_mask; + signal_handler_info.sa_flags = SA_RESTART; + sigaction(SIGPIPE, &signal_handler_info, NULL); + + /* Handle SIGHUP to release all caches */ + sigfillset(&signal_handler_mask); + signal_handler_info.sa_handler = filed_signal_handler; + signal_handler_info.sa_mask = signal_handler_mask; + signal_handler_info.sa_flags = 0; + sigaction(SIGHUP, &signal_handler_info, NULL); /* Initialize cache structure */ cache_ret = filed_init_cache(cache_size); if (cache_ret != 0) { return(cache_ret); @@ -200,11 +314,11 @@ } /* Initialize random number generator */ random_fd = open("/dev/urandom", O_RDONLY); if (random_fd >= 0) { - read(random_fd, &random_value, sizeof(random_value)); + read_ret = read(random_fd, &random_value, sizeof(random_value)); close(random_fd); } random_value ^= getpid(); @@ -212,10 +326,13 @@ random_value ^= time(NULL); srandom(random_value); return(0); + + /* NOTREACH: Read may fail or succeed, we don't actually care */ + read_ret = read_ret; } /* Listen on a particular address/port */ static int filed_listen(const char *address, unsigned int port) { struct sockaddr_in6 addr_v6; @@ -223,11 +340,10 @@ struct sockaddr *addr; socklen_t addr_len; int pton_ret, bind_ret, listen_ret; int family; int fd; - family = AF_INET6; pton_ret = inet_pton(family, address, &addr_v6.sin6_addr.s6_addr); if (pton_ret != 1) { family = AF_INET; @@ -279,12 +395,19 @@ # define filed_log_msg_debug(x, ...) /**/ # define filed_log_msg(x, ...) /**/ # define filed_log_entry(x) /**/ # define filed_log_ip(x, ...) NULL # define filed_log_new(x) &local_dummy_log -# define filed_log_open(x) stdout +# define filed_log_free(x) /**/ + +/* Return logging handle */ +static FILE *filed_log_open(const char *file) { + return(stdout); + file = file; +} #else +# define filed_log_free(x) free(x) # ifdef FILED_DEBUG # define filed_log_msg_debug(x, ...) { fprintf(stderr, x, __VA_ARGS__); fprintf(stderr, "\n"); fflush(stderr); } # else # define filed_log_msg_debug(x, ...) /**/ # endif @@ -341,14 +464,15 @@ if (curr->endtime == ((time_t) -1)) { curr->endtime = now; } - fprintf(fp, "TRANSFER METHOD=%s PATH=%s SRC=%s:%i TIME.START=%llu TIME.END=%llu CODE.VALUE=%u CODE.REASON=%s REQUEST.OFFSET=%llu REQUEST.LENGTH=%llu FILE.LENGTH=%llu TRANSFER.LENGTH=%llu", + fprintf(fp, "TRANSFER METHOD=%s PATH=%s SRC=%s:%i CLIENT.TIME.CONNECT=%llu REQUEST.TIME.START=%llu REQUEST.TIME.END=%llu CODE.VALUE=%u CODE.REASON=%s REQUEST.OFFSET=%llu REQUEST.LENGTH=%llu FILE.LENGTH=%llu TRANSFER.LENGTH=%llu", method, curr->buffer, curr->ip, curr->port, + (unsigned long long) curr->connecttime, (unsigned long long) curr->starttime, (unsigned long long) curr->endtime, curr->http_code, curr->reason, (unsigned long long) curr->req_offset, (unsigned long long) curr->req_length, @@ -357,11 +481,11 @@ ); break; } fprintf(fp, " THREAD=%llu TIME=%llu\n", - (unsigned long long) curr->thread, + (unsigned long long) ((intptr_t) curr->thread), (unsigned long long) now ); fflush(fp); prev = curr; @@ -395,10 +519,11 @@ retval = malloc(sizeof(*retval)); if (initialize) { retval->buffer[0] = '\0'; retval->http_code = -1; + retval->connecttime = 0; retval->starttime = 0; retval->endtime = 0; retval->req_offset = 0; retval->req_length = 0; retval->sent_length = 0; @@ -477,10 +602,212 @@ pthread_mutex_init(&filed_log_msg_list_mutex, NULL); pthread_create(&thread_id, NULL, filed_logging_thread, args); filed_log_msg("START"); + + return(0); +} +#endif + +#ifdef FILED_DONT_TIMEOUT +#define filed_sockettimeout_thread_init() 0 +#define filed_sockettimeout_init() 0 +#define filed_sockettimeout_accept(x) /**/ +#define filed_sockettimeout_processing_start(x) /**/ +#define filed_sockettimeout_processing_end(x) /**/ +#define filed_sockettimeout_close(x, y) /**/ +#else +time_t filed_sockettimeout_time; +struct { + time_t expiration_time; + pthread_t thread_id; + enum { + filed_sockettimeout_valid, + filed_sockettimeout_invalid, + } valid; +} *filed_sockettimeout_sockstatus; +long filed_sockettimeout_sockstatus_length; +int filed_sockettimeout_devnull_fd; +pthread_mutex_t filed_sockettimeout_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int filed_sockettimeout_sockfd_in_range(int sockfd) { + if (sockfd < 3) { + return(0); + } + + if (sockfd > filed_sockettimeout_sockstatus_length) { + return(0); + } + + return(1); +} + +static void filed_sockettimeout_expire(int sockfd, int length, int lockheld) { + time_t now, expire; + + if (!lockheld) { + pthread_mutex_lock(&filed_sockettimeout_mutex); + } + + now = filed_sockettimeout_time; + + expire = now + length; + + filed_sockettimeout_sockstatus[sockfd].expiration_time = expire; + + if (!lockheld) { + pthread_mutex_unlock(&filed_sockettimeout_mutex); + } + + return; +} + +static void filed_sockettimeout_accept(int sockfd) { + if (!filed_sockettimeout_sockfd_in_range(sockfd)) { + return; + } + + pthread_mutex_lock(&filed_sockettimeout_mutex); + + filed_sockettimeout_expire(sockfd, 60, 1); + + filed_sockettimeout_sockstatus[sockfd].thread_id = pthread_self(); + + filed_sockettimeout_sockstatus[sockfd].valid = filed_sockettimeout_valid; + + pthread_mutex_unlock(&filed_sockettimeout_mutex); + + return; +} + +static void filed_sockettimeout_processing_start(int sockfd) { + if (!filed_sockettimeout_sockfd_in_range(sockfd)) { + return; + } + + filed_sockettimeout_expire(sockfd, 86400, 0); + + return; +} + +static void filed_sockettimeout_processing_end(int sockfd) { + if (!filed_sockettimeout_sockfd_in_range(sockfd)) { + return; + } + + filed_sockettimeout_expire(sockfd, 60, 0); + + return; +} + +static void filed_sockettimeout_close(int sockfd, int lockheld) { + if (!filed_sockettimeout_sockfd_in_range(sockfd)) { + return; + } + + if (!lockheld) { + pthread_mutex_lock(&filed_sockettimeout_mutex); + } + + filed_sockettimeout_sockstatus[sockfd].valid = filed_sockettimeout_invalid; + + if (!lockheld) { + pthread_mutex_unlock(&filed_sockettimeout_mutex); + } + + return; +} + +static void *filed_sockettimeout_thread(void *arg) { + struct timespec sleep_time; + time_t now, expiration_time; + pthread_t thread_id; + long idx; + int count; + int valid; + int time_interval = 30; + int check_period = 90; + + while (1) { + for (count = 0; count < (check_period / time_interval); count++) { + sleep_time.tv_sec = time_interval; + sleep_time.tv_nsec = 0; + nanosleep(&sleep_time, NULL); + + pthread_mutex_lock(&filed_sockettimeout_mutex); + + now = time(NULL); + + filed_sockettimeout_time = now; + + pthread_mutex_unlock(&filed_sockettimeout_mutex); + } + + pthread_mutex_lock(&filed_sockettimeout_mutex); + + for (idx = 0; idx < filed_sockettimeout_sockstatus_length; idx++) { + valid = filed_sockettimeout_sockstatus[idx].valid; + + if (valid != filed_sockettimeout_valid) { + continue; + } + + expiration_time = filed_sockettimeout_sockstatus[idx].expiration_time; + + thread_id = filed_sockettimeout_sockstatus[idx].thread_id; + + if (expiration_time > now) { + continue; + } + + filed_sockettimeout_close(idx, 1); + + dup2(filed_sockettimeout_devnull_fd, idx); + + pthread_kill(thread_id, SIGPIPE); + } + + pthread_mutex_unlock(&filed_sockettimeout_mutex); + } + + return(NULL); + + /* NOTREACH: We don't actually take any arguments */ + arg = arg; +} + +static int filed_sockettimeout_thread_init(void) { + pthread_t thread_id; + + pthread_create(&thread_id, NULL, filed_sockettimeout_thread, NULL); + + return(0); +} + +static int filed_sockettimeout_init(void) { + long maxfd, idx; + + maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd <= 0) { + maxfd = 4096; + } + + filed_sockettimeout_sockstatus_length = maxfd; + filed_sockettimeout_sockstatus = malloc(sizeof(*filed_sockettimeout_sockstatus) * filed_sockettimeout_sockstatus_length); + if (filed_sockettimeout_sockstatus == NULL) { + return(-1); + } + + for (idx = 0; idx < maxfd; idx++) { + filed_sockettimeout_sockstatus[idx].valid = filed_sockettimeout_invalid; + } + + filed_sockettimeout_devnull_fd = open("/dev/null", O_RDWR); + if (filed_sockettimeout_devnull_fd < 0) { + return(-1); + } return(0); } #endif @@ -564,34 +891,88 @@ (unsigned long long) random(), (unsigned long long) random(), (unsigned long long) random() ); } + +#ifdef FILED_FAKE_CHROOT +/* Translate a path into a fake chroot path */ +static const char *filed_path_translate(const char *path, struct filed_options *options) { + static __thread char pathBuffer[8192]; + int snprintf_ret; + + /* If no alternative root is specified, return the unadorned path */ + if (!options->fake_newroot) { + return(path); + } + + /* Verify that this request will not go outside of the specified root */ + if (strstr(path, "/../") != NULL || path[0] != '/') { + filed_log_msg_debug("Unable to translate path \"%s\", contains invalid characters", path); + + return(options->fake_newroot); + } + + /* Create the new path into our local (TLS) static buffer */ + snprintf_ret = snprintf(pathBuffer, sizeof(pathBuffer), "%s/%s", options->fake_newroot, path); + if (snprintf_ret < 0 || ((unsigned int) snprintf_ret) >= sizeof(pathBuffer)) { + filed_log_msg_debug("Unable to translate path \"%s\", will not fit into new buffer", path); + + return(options->fake_newroot); + } + + filed_log_msg_debug("Translating path \"%s\" into \"%s\"", path, pathBuffer); + + /* Return the new path */ + return(pathBuffer); +} + +static void filed_path_translate_set_root(const char *var, struct filed_options *options, const char *val) { + options->fake_newroot = strdup(val); + + return; + + /* var is only used in the macro -- discard it here */ + var = var; +} +#else +#define filed_path_translate(path, options) path +#define filed_path_translate_set_root(var, options, val) var = strdup(val) +#endif /* Open a file and return file information */ -static struct filed_fileinfo *filed_open_file(const char *path, struct filed_fileinfo *buffer) { +static struct filed_fileinfo *filed_open_file(const char *path, struct filed_fileinfo *buffer, struct filed_options *options) { struct filed_fileinfo *cache; unsigned int cache_idx; off_t len; int fd; - cache_idx = filed_hash((const unsigned char *) path, filed_fileinfo_fdcache_size); - - cache = &filed_fileinfo_fdcache[cache_idx]; - - filed_log_msg_debug("Locking mutex for idx: %lu", (unsigned long) cache_idx); - - pthread_mutex_lock(&cache->mutex); - - filed_log_msg_debug("Completed locking mutex for idx: %lu", (unsigned long) cache_idx); + if (filed_fileinfo_fdcache_size != 0) { + cache_idx = filed_hash((const unsigned char *) path, filed_fileinfo_fdcache_size); + + cache = &filed_fileinfo_fdcache[cache_idx]; + + filed_log_msg_debug("Locking mutex for idx: %lu", (unsigned long) cache_idx); + + pthread_mutex_lock(&cache->mutex); + + filed_log_msg_debug("Completed locking mutex for idx: %lu", (unsigned long) cache_idx); + } else { + cache_idx = 0; + cache = buffer; + cache->path[0] = '\0'; + cache->fd = -1; + } if (strcmp(path, cache->path) != 0) { filed_log_msg_debug("Cache miss for idx: %lu: OLD \"%s\", NEW \"%s\"", (unsigned long) cache_idx, cache->path, path); - fd = open(path, O_RDONLY | O_LARGEFILE); + fd = open(filed_path_translate(path, options), O_RDONLY | O_LARGEFILE); if (fd < 0) { - pthread_mutex_unlock(&cache->mutex); + if (filed_fileinfo_fdcache_size != 0) { + pthread_mutex_unlock(&cache->mutex); + } return(NULL); } if (cache->fd >= 0) { @@ -611,31 +992,36 @@ cache->lastmod = filed_format_time(cache->lastmod_b, sizeof(cache->lastmod_b), time(NULL) - 30); } else { filed_log_msg_debug("Cache hit for idx: %lu: PATH \"%s\"", (unsigned long) cache_idx, path); } - /* - * We have to make a duplicate FD, because once we release the cache - * mutex, the file descriptor may be closed - */ - fd = dup(cache->fd); - if (fd < 0) { - pthread_mutex_unlock(&cache->mutex); - - return(NULL); - } - - buffer->fd = fd; - buffer->len = cache->len; - buffer->type = cache->type; - memcpy(buffer->lastmod_b, cache->lastmod_b, sizeof(buffer->lastmod_b)); - memcpy(buffer->etag, cache->etag, sizeof(buffer->etag)); - buffer->lastmod = buffer->lastmod_b + (cache->lastmod - cache->lastmod_b); - - pthread_mutex_unlock(&cache->mutex); + if (filed_fileinfo_fdcache_size != 0) { + /* + * We have to make a duplicate FD, because once we release the cache + * mutex, the file descriptor may be closed + */ + fd = dup(cache->fd); + if (fd < 0) { + pthread_mutex_unlock(&cache->mutex); + + return(NULL); + } + + buffer->fd = fd; + buffer->len = cache->len; + buffer->type = cache->type; + memcpy(buffer->lastmod_b, cache->lastmod_b, sizeof(buffer->lastmod_b)); + memcpy(buffer->etag, cache->etag, sizeof(buffer->etag)); + buffer->lastmod = buffer->lastmod_b + (cache->lastmod - cache->lastmod_b); + + pthread_mutex_unlock(&cache->mutex); + } return(buffer); + + /* options is only used if fake chroot is enabled, confuse the compiler */ + options = options; } /* Process an HTTP request and return the path requested */ static struct filed_http_request *filed_get_http_request(FILE *fp, struct filed_http_request *buffer_st, struct filed_options *options) { char *method, *path; @@ -651,10 +1037,11 @@ range_start = 0; range_end = 0; range_request = 0; range_length = -1; buffer_st->headers.host.present = 0; + buffer_st->headers.connection = FILED_CONNECTION_CLOSE; buffer = buffer_st->tmpbuf; buffer_len = sizeof(buffer_st->tmpbuf); fgets_ret = fgets(buffer, buffer_len, fp); @@ -750,10 +1137,12 @@ while (*workbuffer == ' ') { workbuffer++; } strcpy(buffer_st->headers.host.host, workbuffer); + } else if (strncasecmp(buffer, "Connection: Keep-Alive", 22) == 0) { + buffer_st->headers.connection = FILED_CONNECTION_KEEP_ALIVE; } if (memcmp(buffer, "\r\n", 2) == 0) { break; } @@ -824,18 +1213,21 @@ log->http_code = error_number; filed_log_entry(log); /* Close connection */ + filed_sockettimeout_close(fileno(fp), 0); + fclose(fp); return; } /* Return a redirect to index.html */ +#ifndef FILED_DONT_REDIRECT_DIRECTORIES static void filed_redirect_index(FILE *fp, const char *date_current, const char *path, struct filed_log_entry *log) { - int http_code = 301; + int http_code = 302; fprintf(fp, "HTTP/1.1 %i OK\r\nDate: %s\r\nServer: filed\r\nLast-Modified: %s\r\nContent-Length: 0\r\nConnection: close\r\nLocation: %s\r\n\r\n", http_code, date_current, date_current, "index.html" @@ -846,82 +1238,119 @@ log->http_code = http_code; filed_log_entry(log); /* Close connection */ + filed_sockettimeout_close(fileno(fp), 0); + fclose(fp); return; /* Currently unused: path */ path = path; } +#endif + +/* Convert an enum representing the "Connection" header value to a string */ +static const char *filed_connection_str(int connection_value) { + switch (connection_value) { + case FILED_CONNECTION_CLOSE: + return("close"); + case FILED_CONNECTION_KEEP_ALIVE: + return("keep-alive"); + } + + return("close"); +} /* Handle a single request from a client */ -static void filed_handle_client(int fd, struct filed_http_request *request, struct filed_log_entry *log, struct filed_options *options) { +static int filed_handle_client(int fd, struct filed_http_request *request, struct filed_log_entry *log, struct filed_options *options) { struct filed_fileinfo *fileinfo; ssize_t sendfile_ret; size_t sendfile_size; off_t sendfile_offset, sendfile_sent, sendfile_len; char *path; char *date_current, date_current_b[64]; int http_code; FILE *fp; + /* Indicate the connection start time */ + log->connecttime = time(NULL); + /* Determine current time */ date_current = filed_format_time(date_current_b, sizeof(date_current_b), time(NULL)); /* Open socket as ANSI I/O for ease of use */ fp = fdopen(fd, "w+b"); if (fp == NULL) { + filed_sockettimeout_close(fd, 0); + close(fd); log->buffer[0] = '\0'; log->http_code = -1; log->reason = "fdopen_failed"; filed_log_entry(log); - return; + return(FILED_CONNECTION_CLOSE); } request = filed_get_http_request(fp, request, options); if (request == NULL) { log->buffer[0] = '\0'; filed_error_page(fp, date_current, 500, FILED_REQUEST_METHOD_GET, "format", log); - return; + return(FILED_CONNECTION_CLOSE); } + + filed_sockettimeout_processing_start(fd); path = request->path; strcpy(log->buffer, path); log->method = request->method; /* If the requested path is a directory, redirect to index page */ if (request->type == FILED_REQUEST_TYPE_DIRECTORY) { +#ifdef FILED_DONT_REDIRECT_DIRECTORIES + char localpath[8192]; + int snprintf_ret; + + snprintf_ret = snprintf(localpath, sizeof(localpath), "%s/index.html", path); + + if (snprintf_ret <= 0 || snprintf_ret > (sizeof(localpath) - 1)) { + filed_error_page(fp, date_current, 500, request->method, "path_format", log); + + return(FILED_CONNECTION_CLOSE); + } + + path = localpath; +#else filed_redirect_index(fp, date_current, path, log); - return; + return(FILED_CONNECTION_CLOSE); +#endif } - fileinfo = filed_open_file(path, &request->fileinfo); + fileinfo = filed_open_file(path, &request->fileinfo, options); if (fileinfo == NULL) { filed_error_page(fp, date_current, 404, request->method, "open_failed", log); - return; + return(FILED_CONNECTION_CLOSE); } if (request->headers.range.present) { if (request->headers.range.offset != 0 || request->headers.range.length >= 0) { if (request->headers.range.offset >= fileinfo->len) { filed_error_page(fp, date_current, 416, request->method, "range_invalid", log); close(fileinfo->fd); - return; + return(FILED_CONNECTION_CLOSE); } if (request->headers.range.length == ((off_t) -1)) { filed_log_msg_debug("Computing length to fit in bounds: fileinfo->len = %llu, request->headers.range.offset = %llu", (unsigned long long) fileinfo->len, @@ -945,16 +1374,17 @@ /* Compute fake range parameters that includes the entire file */ request->headers.range.offset = 0; request->headers.range.length = fileinfo->len; } - fprintf(fp, "HTTP/1.1 %i OK\r\nDate: %s\r\nServer: filed\r\nLast-Modified: %s\r\nContent-Length: %llu\r\nAccept-Ranges: bytes\r\nContent-Type: %s\r\nConnection: close\r\nETag: \"%s\"\r\n", + fprintf(fp, "HTTP/1.1 %i OK\r\nDate: %s\r\nServer: filed\r\nLast-Modified: %s\r\nContent-Length: %llu\r\nAccept-Ranges: bytes\r\nContent-Type: %s\r\nConnection: %s\r\nETag: \"%s\"\r\n", http_code, date_current, fileinfo->lastmod, (unsigned long long) request->headers.range.length, fileinfo->type, + filed_connection_str(request->headers.connection), fileinfo->etag ); if (http_code == 206) { fprintf(fp, "Content-Range: bytes %llu-%llu/%llu\r\n", @@ -1043,13 +1473,21 @@ filed_log_entry(log); close(fileinfo->fd); - fclose(fp); + if (request->headers.connection != FILED_CONNECTION_KEEP_ALIVE) { + filed_sockettimeout_close(fd, 0); - return; + fclose(fp); + + return(FILED_CONNECTION_CLOSE); + } + + filed_sockettimeout_processing_end(fd); + + return(FILED_CONNECTION_KEEP_ALIVE); } /* Handle incoming connections */ static void *filed_worker_thread(void *arg_v) { struct filed_worker_thread_args *arg; @@ -1057,11 +1495,12 @@ struct filed_log_entry *log, local_dummy_log; struct filed_options *options; struct sockaddr_in6 addr; socklen_t addrlen; int failure_count = 0, max_failure_count = FILED_MAX_FAILURE_COUNT; - int master_fd, fd; + int connection_state = FILED_CONNECTION_CLOSE; + int master_fd, fd = -1; /* Read arguments */ arg = arg_v; master_fd = arg->fd; @@ -1081,13 +1520,17 @@ break; } log->type = FILED_LOG_TYPE_TRANSFER; - /* Accept a new client */ - addrlen = sizeof(addr); - fd = accept(master_fd, (struct sockaddr *) &addr, &addrlen); + /* If we closed the old connection, accept a new one */ + if (connection_state == FILED_CONNECTION_CLOSE) { + /* Accept a new client */ + addrlen = sizeof(addr); + + fd = accept(master_fd, (struct sockaddr *) &addr, &addrlen); + } /* * If we fail, make a note of it so we don't go into a loop of * accept() failing */ @@ -1095,14 +1538,16 @@ /* Log the new connection */ filed_log_msg("ACCEPT_FAILED"); failure_count++; - free(log); + filed_log_free(log); continue; } + + filed_sockettimeout_accept(fd); /* Fill in log structure */ if (filed_log_ip((struct sockaddr *) &addr, log->ip, sizeof(log->ip)) == NULL) { log->ip[0] = '\0'; log->port = 0; @@ -1112,11 +1557,11 @@ /* Reset failure count*/ failure_count = 0; /* Handle socket */ - filed_handle_client(fd, &request, log, options); + connection_state = filed_handle_client(fd, &request, log, options); } /* Report error */ filed_log_msg("THREAD_DIED ABNORMAL"); @@ -1331,11 +1776,11 @@ } /* Run process */ int main(int argc, char **argv) { struct option options[12]; - struct filed_options thread_options; + struct filed_options thread_options = {0}; const char *bind_addr = BIND_ADDR, *newroot = NULL, *log_file = LOG_FILE; FILE *log_fp; uid_t user = 0; int port = PORT, thread_count = THREAD_COUNT; int cache_size = CACHE_SIZE; @@ -1342,13 +1787,10 @@ int init_ret, chroot_ret, setuid_ret, lookup_ret, chdir_ret; int setuid_enabled = 0, daemon_enabled = 0; int ch; int fd; - /* Set default values */ - thread_options.vhosts_enabled = 0; - /* Process arguments */ filed_getopt_long_setopt(&options[0], "port", required_argument, 'p'); filed_getopt_long_setopt(&options[1], "threads", required_argument, 't'); filed_getopt_long_setopt(&options[2], "cache", required_argument, 'c'); filed_getopt_long_setopt(&options[3], "bind", required_argument, 'b'); @@ -1382,11 +1824,11 @@ return(1); } break; case 'r': - newroot = strdup(optarg); + filed_path_translate_set_root(newroot, &thread_options, optarg); break; case 'l': log_file = strdup(optarg); break; case 'd': @@ -1425,10 +1867,18 @@ if (fd < 0) { perror("filed_listen"); return(1); } + + /* Initialize timeout structures */ + init_ret = filed_sockettimeout_init(); + if (init_ret != 0) { + perror("filed_sockettimeout_init"); + + return(8); + } /* Become a daemon */ if (daemon_enabled) { init_ret = filed_daemonize(); if (init_ret != 0) { @@ -1481,10 +1931,18 @@ if (init_ret != 0) { perror("filed_logging_thread_init"); return(4); } + + /* Create socket termination thread */ + init_ret = filed_sockettimeout_thread_init(); + if (init_ret != 0) { + perror("filed_sockettimeout_thread_init"); + + return(7); + } /* Create worker threads */ init_ret = filed_worker_threads_init(fd, thread_count, &thread_options); if (init_ret != 0) { perror("filed_worker_threads_init"); @@ -1493,11 +1951,11 @@ } /* Wait for threads to exit */ /* XXX:TODO: Monitor thread usage */ while (1) { - sleep(60); + sleep(86400); } /* Return in failure */ return(2); } Index: generate-mime-types ================================================================== --- generate-mime-types +++ generate-mime-types @@ -42,13 +42,23 @@ return $retval } # Read contents of mime types file -set fd [open $mimeinfofile] -set mimeinfo [read $fd] -close $fd +catch { + set fd [open $mimeinfofile] + set mimeinfo [read $fd] + close $fd +} + +if {![info exists mimeinfo]} { + puts stderr "Not using $mimeinfofile, unreadable." + + exit 1 +} + +puts stderr "Using $mimeinfofile as mime.types" # Parse into type and extensions pairs foreach line [split $mimeinfo "\n"] { regsub {#.*} $line {} line set line [string trim $line] ADDED mime.types Index: mime.types ================================================================== --- /dev/null +++ mime.types @@ -0,0 +1,5 @@ +text/html html htm +test/plain txt text +video/mp4 mp4 mpg4 +audio/mpeg mp3 mpg3 +application/zip zip