diff --git a/.config/imapfilter/config.lua b/.config/imapfilter/config.lua new file mode 100644 index 00000000..e086a571 --- /dev/null +++ b/.config/imapfilter/config.lua @@ -0,0 +1,52 @@ +-- According to the IMAP specification, when trying to write a message +-- to a non-existent mailbox, the server must send a hint to the client, +-- whether it should create the mailbox and try again or not. However +-- some IMAP servers don't follow the specification and don't send the +-- correct response code to the client. By enabling this option the +-- client tries to create the mailbox, despite of the server's response. +-- This variable takes a boolean as a value. Default is “false”. +options.create = true +-- By enabling this option new mailboxes that were automatically created, +-- get auto subscribed +options.subscribe = true +-- How long to wait for servers response. +options.timeout = 120 + +function trim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +-- Gets password from pass +status, dom_password = pipe_from('pass show Email/privateemail.com/yigit@yigitcolakoglu.com') +domain = IMAP { + server = "mail.privateemail.com", + port = 143, + username = "yigit@yigitcolakoglu.com", + password = trim(dom_password ), + ssl = auto +} + +-- Gets password from pass +status, hot_password = pipe_from('pass show AppPass/microsoft.com/yigitcolakoglu@hotmail.com') +-- Setup an imap account called hotmail +hotmail = IMAP { + server = "outlook.office365.com", + port = 143, + username = "yigitcolakoglu@hotmail.com", + password = trim(hot_password), + ssl = auto +} + +-- Block fucking Aleksandr. LEAVE ME ALONE DUDE + +function fuckAleksandr() + mailboxes, folders = domain:list_all("/") + for _, v in pairs(mailboxes) do + messages = domain[v]:contain_subject("Предложение") + messages:delete_messages() + end +end + +print("Fuck Aleksandr") +fuckAleksandr {} +print("Done fucking Aleksandr") diff --git a/.config/vim/plugin/csyntaxafter.vim b/.config/vim/plugin/csyntaxafter.vim deleted file mode 100644 index 6bf1f21c..00000000 --- a/.config/vim/plugin/csyntaxafter.vim +++ /dev/null @@ -1 +0,0 @@ -autocmd! FileType c,cpp,java,php call CSyntaxAfter() diff --git a/.config/weechat/irc.conf b/.config/weechat/irc.conf index 8a25f274..90f3e767 100644 --- a/.config/weechat/irc.conf +++ b/.config/weechat/irc.conf @@ -188,7 +188,7 @@ freenode.local_hostname freenode.usermode freenode.command freenode.command_delay -freenode.autojoin = "#vim,#archlinux,#lf,##C,#python,##java,#xorg-devel,#pass" +freenode.autojoin = "#vim,#gentoo,#lf,##C,#python,##java,#xorg-devel,#pass" freenode.autorejoin freenode.autorejoin_delay freenode.connection_timeout diff --git a/.config/weechat/sec.conf b/.config/weechat/sec.conf index 60808481..93e4c872 100644 --- a/.config/weechat/sec.conf +++ b/.config/weechat/sec.conf @@ -17,7 +17,7 @@ salt = on [data] __passphrase__ = on -oftc_password = "7EDB64DA64B13CC9ACEE277089CA61B4F3F1DCB04FA16CEFDA10AAC151B2CBF1C22D9AC25CC4F84F13DBD7EC497D554995AE7298B88670" -sec.data.oftc_password = "55893BA77CE986F45826C39825BDA41E84F5CF6258853AD74648C617EF30AA9CCF00179E5975FFC39450A635994B588CCF94FA8BD220A25BE25600E75A4FDA0A2992" -freenode = "94AAE6F37BC7B07C7B34F57F0CE581F966BE2919404337819E8228F99C412BC7D0FDF6FFDC7CBADBC64F646B6926EB3DA5AEAC72BB1D1511B836AF" -rizon_password = "21D6088B8200A24BE75C7A671308EED61759E688ABBBCC2E47463D127622D9CF98E6793E17FB8FF0081C82C889EFF61D376FD75280BB71429FE7387BA8FB15AF7F78" +oftc_password = "936C665BADDD99B55848AE19992B3873FBE68A074B1CE52CED6844A3CA3560376116A605980F02C7F9C8ED139044FBDA77090ED6953197" +sec.data.oftc_password = "E03D86B8F0B9601B5EC92FEA1A564671F3CFEBF7B63539878A6BC028FB906790199D450D342F019C493DB31BF6BC59C144270F1C7495FDB6739626B615404CF59FDD" +freenode = "06841832E731EB8D00293CCB6334BD328B117D661E99279ADE699F8331387315C713F852E3C65AB3AF6A3D6A401F05B456CE6032DBF30B8F244799" +rizon_password = "3F6AAECFAA67DBC3636B60B3E05695F354158BB329F90F51320808BF06A3088D6C727CCA642332A96E0DF79CA15176B07B89E38D31F553D72B6D1C1E117F7EBDDFEE" diff --git a/.config/zsh/aliases b/.config/zsh/aliases index 8aa4db4e..34f7c4de 100755 --- a/.config/zsh/aliases +++ b/.config/zsh/aliases @@ -43,6 +43,7 @@ alias yarn="yarn --use-yarnrc $XDG_CONFIG_HOME/yarn/config" alias tmate="tmate -f $XDG_CONFIG_HOME/tmate/tmate.conf" alias mc="mc --config-dir=$XDG_CONFIG_HOME/mc" alias abook="abook --config \"$XDG_CONFIG_HOME\"/abook/abookrc --datafile \"$XDG_DATA_HOME\"/abook/addressbook" +alias imapfilter="imapfilter -c \"$IMAPFILTER_CONFIG\"" alias dots="git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME" alias dpall="dots remote | xargs -I R git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME push R" diff --git a/.local/bin/mailsync b/.local/bin/mailsync index e433d2f9..a62ba7d6 100755 --- a/.local/bin/mailsync +++ b/.local/bin/mailsync @@ -21,7 +21,7 @@ pgrep -x mbsync >/dev/null && { echo "mbsync is already running." ; exit ;} # files for variable assignments. This is ugly, but there are few options that # will work on the maximum number of machines. eval "$(grep -h -- \ - "^\s*\(export \)\?\(MBSYNCRC\|PASSWORD_STORE_DIR\|NOTMUCH_CONFIG\|GNUPGHOME\|XDG_DATA_HOME\|XDG_CONFIG_HOME\|XDG_RUNTIME_DIR\|XDG_CACHE_HOME\)=" \ + "^\s*\(export \)\?\(MBSYNCRC\|IMAPFILTER_CONFIG\|PASSWORD_STORE_DIR\|NOTMUCH_CONFIG\|GNUPGHOME\|XDG_DATA_HOME\|XDG_CONFIG_HOME\|XDG_RUNTIME_DIR\|XDG_CACHE_HOME\)=" \ "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.zprofile" "$HOME/.config/zsh/.zprofile" "$HOME/.zshenv" \ "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/zsh/.zshrc" "$HOME/.pam_environment" 2>/dev/null)" @@ -77,6 +77,8 @@ syncandnotify() { fi } +imapfilter -c "$IMAPFILTER_CONFIG" + # Sync accounts passed as argument or all. if [ "$#" -eq "0" ]; then accounts="$(awk '/^Channel/ {print $2}' "$MBSYNCRC")" diff --git a/.local/share/dwm/autostart.sh b/.local/share/dwm/autostart.sh index e917b563..10177957 100755 --- a/.local/share/dwm/autostart.sh +++ b/.local/share/dwm/autostart.sh @@ -1,6 +1,5 @@ #!/bin/sh - ~/.local/bin/daily-update redshift -x 2> /dev/null > /dev/null @@ -53,3 +52,4 @@ tmux new-session -s weechat -d weechat > /dev/null 2> /dev/null clipmenud > $XDG_RUNTIME_DIR/clipmenud.out 2> $XDG_RUNTIME_DIR/clipmenud.err & rm -f ~/.surf/tabbed-surf.xid /bin/polkit-dumb-agent & +darkhttpd $HOME/.local/share/startpage/dist --port 9999 --daemon --addr 127.0.0.1 diff --git a/.local/src/darkhttpd/.gitignore b/.local/src/darkhttpd/.gitignore new file mode 100644 index 00000000..2da7127e --- /dev/null +++ b/.local/src/darkhttpd/.gitignore @@ -0,0 +1 @@ +darkhttpd diff --git a/.local/src/darkhttpd/Makefile b/.local/src/darkhttpd/Makefile new file mode 100644 index 00000000..2708b3d0 --- /dev/null +++ b/.local/src/darkhttpd/Makefile @@ -0,0 +1,19 @@ +CC?=cc +CFLAGS?=-O +LIBS=`[ \`uname\` = "SunOS" ] && echo -lsocket -lnsl` + +all: darkhttpd + +install: darkhttpd + cp darkhttpd /usr/bin/darkhttpd + +darkhttpd: darkhttpd.c + $(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) darkhttpd.c -o $@ + +darkhttpd-static: darkhttpd.c + $(CC) -static $(CFLAGS) $(LDFLAGS) $(LIBS) darkhttpd.c -o $@ + +clean: + rm -f darkhttpd core darkhttpd.core darkhttpd-static darkhttpd-static.core + +.PHONY: all clean diff --git a/.local/src/darkhttpd/darkhttpd.c b/.local/src/darkhttpd/darkhttpd.c new file mode 100644 index 00000000..268628a8 --- /dev/null +++ b/.local/src/darkhttpd/darkhttpd.c @@ -0,0 +1,2853 @@ +/* darkhttpd - a simple, single-threaded, static content webserver. + * https://unix4lyfe.org/darkhttpd/ + * Copyright (c) 2003-2021 Emil Mikulic + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +static const char + pkgname[] = "darkhttpd/1.13.from.git", + copyright[] = "copyright (c) 2003-2021 Emil Mikulic"; + +/* Possible build options: -DDEBUG -DNO_IPV6 */ + +#ifndef NO_IPV6 +# define HAVE_INET6 +#endif + +#ifndef DEBUG +# define NDEBUG +static const int debug = 0; +#else +static const int debug = 1; +#endif + +#ifdef __linux +# define _GNU_SOURCE /* for strsignal() and vasprintf() */ +# define _FILE_OFFSET_BITS 64 /* stat() files bigger than 2GB */ +# include +#endif + +#ifdef __sun__ +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# include +# endif +#endif + +#ifdef __sun__ +# ifndef INADDR_NONE +# define INADDR_NONE -1 +# endif +#endif + +#ifndef MAXNAMLEN +# ifdef NAME_MAX +# define MAXNAMLEN NAME_MAX +# else +# define MAXNAMLEN 255 +# endif +#endif + +#if defined(O_EXCL) && !defined(O_EXLOCK) +# define O_EXLOCK O_EXCL +#endif + +#ifndef __printflike +# ifdef __GNUC__ +/* [->] borrowed from FreeBSD's src/sys/sys/cdefs.h,v 1.102.2.2.2.1 */ +# define __printflike(fmtarg, firstvararg) \ + __attribute__((__format__(__printf__, fmtarg, firstvararg))) +/* [<-] */ +# else +# define __printflike(fmtarg, firstvararg) +# endif +#endif + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +# define unused __attribute__((__unused__)) +#else +# define unused +#endif + +/* [->] borrowed from FreeBSD's src/sys/sys/systm.h,v 1.276.2.7.4.1 */ +#ifndef CTASSERT /* Allow lint to override */ +# define CTASSERT(x) _CTASSERT(x, __LINE__) +# define _CTASSERT(x, y) __CTASSERT(x, y) +# define __CTASSERT(x, y) typedef char __assert ## y[(x) ? 1 : -1] +#endif +/* [<-] */ + +CTASSERT(sizeof(unsigned long long) >= sizeof(off_t)); +#define llu(x) ((unsigned long long)(x)) + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__linux) +# include +#else +/* err - prints "error: format: strerror(errno)" to stderr and exit()s with + * the given code. + */ +static void err(const int code, const char *format, ...) __printflike(2, 3); +static void err(const int code, const char *format, ...) { + va_list va; + + va_start(va, format); + fprintf(stderr, "error: "); + vfprintf(stderr, format, va); + fprintf(stderr, ": %s\n", strerror(errno)); + va_end(va); + exit(code); +} + +/* errx - err() without the strerror */ +static void errx(const int code, const char *format, ...) __printflike(2, 3); +static void errx(const int code, const char *format, ...) { + va_list va; + + va_start(va, format); + fprintf(stderr, "error: "); + vfprintf(stderr, format, va); + fprintf(stderr, "\n"); + va_end(va); + exit(code); +} + +/* warn - err() without the exit */ +static void warn(const char *format, ...) __printflike(1, 2); +static void warn(const char *format, ...) { + va_list va; + + va_start(va, format); + fprintf(stderr, "warning: "); + vfprintf(stderr, format, va); + fprintf(stderr, ": %s\n", strerror(errno)); + va_end(va); +} +#endif + +/* [->] LIST_* macros taken from FreeBSD's src/sys/sys/queue.h,v 1.56 + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Under a BSD license. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +#define LIST_FIRST(head) ((head)->lh_first) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ +} while (0) + +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_REMOVE(elm, field) do { \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ +} while (0) +/* [<-] */ + +static LIST_HEAD(conn_list_head, connection) connlist = + LIST_HEAD_INITIALIZER(conn_list_head); + +struct connection { + LIST_ENTRY(connection) entries; + + int socket; +#ifdef HAVE_INET6 + struct in6_addr client; +#else + in_addr_t client; +#endif + time_t last_active; + enum { + RECV_REQUEST, /* receiving request */ + SEND_HEADER, /* sending generated header */ + SEND_REPLY, /* sending reply */ + DONE /* connection closed, need to remove from queue */ + } state; + + /* char request[request_length+1] is null-terminated */ + char *request; + size_t request_length; + + /* request fields */ + char *method, *url, *referer, *user_agent, *authorization; + off_t range_begin, range_end; + off_t range_begin_given, range_end_given; + + char *header; + size_t header_length, header_sent; + int header_dont_free, header_only, http_code, conn_close; + + enum { REPLY_GENERATED, REPLY_FROMFILE } reply_type; + char *reply; + int reply_dont_free; + int reply_fd; + off_t reply_start, reply_length, reply_sent, + total_sent; /* header + body = total, for logging */ +}; + +struct forward_mapping { + const char *host, *target_url; /* These point at argv. */ +}; + +static struct forward_mapping *forward_map = NULL; +static size_t forward_map_size = 0; +static const char *forward_all_url = NULL; + +struct mime_mapping { + char *extension, *mimetype; +}; + +static struct mime_mapping *mime_map = NULL; +static size_t mime_map_size = 0; +static size_t longest_ext = 0; + +/* If a connection is idle for timeout_secs or more, it gets closed and + * removed from the connlist. + */ +static int timeout_secs = 30; +static char *keep_alive_field = NULL; + +/* Time is cached in the event loop to avoid making an excessive number of + * gettimeofday() calls. + */ +static time_t now; + +/* To prevent a malformed request from eating up too much memory, die once the + * request exceeds this many bytes: + */ +#define MAX_REQUEST_LENGTH 4000 + +/* Defaults can be overridden on the command-line */ +static const char *bindaddr; +static uint16_t bindport = 8080; /* or 80 if running as root */ +static int max_connections = -1; /* kern.ipc.somaxconn */ +static const char *index_name = "index.html"; +static int no_listing = 0; + +static int sockin = -1; /* socket to accept connections from */ +#ifdef HAVE_INET6 +static int inet6 = 0; /* whether the socket uses inet6 */ +#endif +static char *wwwroot = NULL; /* a path name */ +static char *logfile_name = NULL; /* NULL = no logging */ +static FILE *logfile = NULL; +static char *pidfile_name = NULL; /* NULL = no pidfile */ +static int want_chroot = 0, want_daemon = 0, want_accf = 0, + want_keepalive = 1, want_server_id = 1; +static char *server_hdr = NULL; +static char *auth_key = NULL; +static uint64_t num_requests = 0, total_in = 0, total_out = 0; +static int accepting = 1; /* set to 0 to stop accept()ing */ +static int syslog_enabled = 0; +static volatile int running = 1; /* signal handler sets this to false */ + +#define INVALID_UID ((uid_t) -1) +#define INVALID_GID ((gid_t) -1) + +static uid_t drop_uid = INVALID_UID; +static gid_t drop_gid = INVALID_GID; + +/* Default mimetype mappings - make sure this array is NULL terminated. */ +static const char *default_extension_map[] = { + "application/ogg" " ogg", + "application/pdf" " pdf", + "application/wasm" " wasm", + "application/xml" " xsl xml", + "application/xml-dtd" " dtd", + "application/xslt+xml" " xslt", + "application/zip" " zip", + "audio/mpeg" " mp2 mp3 mpga", + "image/gif" " gif", + "image/jpeg" " jpeg jpe jpg", + "image/png" " png", + "image/svg+xml" " svg", + "text/css" " css", + "text/html" " html htm", + "text/javascript" " js", + "text/plain" " txt asc", + "video/mpeg" " mpeg mpe mpg", + "video/quicktime" " qt mov", + "video/x-msvideo" " avi", + "video/mp4" " mp4", + NULL +}; + +static const char octet_stream[] = "application/octet-stream"; +static const char *default_mimetype = octet_stream; + +/* Prototypes. */ +static void poll_recv_request(struct connection *conn); +static void poll_send_header(struct connection *conn); +static void poll_send_reply(struct connection *conn); + +/* close() that dies on error. */ +static void xclose(const int fd) { + if (close(fd) == -1) + err(1, "close()"); +} + +/* malloc that dies if it can't allocate. */ +static void *xmalloc(const size_t size) { + void *ptr = malloc(size); + if (ptr == NULL) + errx(1, "can't allocate %zu bytes", size); + return ptr; +} + +/* realloc() that dies if it can't reallocate. */ +static void *xrealloc(void *original, const size_t size) { + void *ptr = realloc(original, size); + if (ptr == NULL) + errx(1, "can't reallocate %zu bytes", size); + return ptr; +} + +/* strdup() that dies if it can't allocate. + * Implement this ourselves since regular strdup() isn't C89. + */ +static char *xstrdup(const char *src) { + size_t len = strlen(src) + 1; + char *dest = xmalloc(len); + memcpy(dest, src, len); + return dest; +} + +#ifdef __sun /* unimpressed by Solaris */ +static int vasprintf(char **strp, const char *fmt, va_list ap) { + char tmp; + int result = vsnprintf(&tmp, 1, fmt, ap); + *strp = xmalloc(result+1); + result = vsnprintf(*strp, result+1, fmt, ap); + return result; +} +#endif + +/* vasprintf() that dies if it fails. */ +static unsigned int xvasprintf(char **ret, const char *format, va_list ap) + __printflike(2,0); +static unsigned int xvasprintf(char **ret, const char *format, va_list ap) { + int len = vasprintf(ret, format, ap); + if (ret == NULL || len == -1) + errx(1, "out of memory in vasprintf()"); + return (unsigned int)len; +} + +/* asprintf() that dies if it fails. */ +static unsigned int xasprintf(char **ret, const char *format, ...) + __printflike(2,3); +static unsigned int xasprintf(char **ret, const char *format, ...) { + va_list va; + unsigned int len; + + va_start(va, format); + len = xvasprintf(ret, format, va); + va_end(va); + return len; +} + +/* Append buffer code. A somewhat efficient string buffer with pool-based + * reallocation. + */ +#ifndef APBUF_INIT +# define APBUF_INIT 4096 +#endif +#define APBUF_GROW APBUF_INIT +struct apbuf { + size_t length, pool; + char *str; +}; + +static struct apbuf *make_apbuf(void) { + struct apbuf *buf = xmalloc(sizeof(struct apbuf)); + buf->length = 0; + buf->pool = APBUF_INIT; + buf->str = xmalloc(buf->pool); + return buf; +} + +/* Append s (of length len) to buf. */ +static void appendl(struct apbuf *buf, const char *s, const size_t len) { + size_t need = buf->length + len; + if (buf->pool < need) { + /* pool has dried up */ + while (buf->pool < need) + buf->pool += APBUF_GROW; + buf->str = xrealloc(buf->str, buf->pool); + } + memcpy(buf->str + buf->length, s, len); + buf->length += len; +} + +#ifdef __GNUC__ +#define append(buf, s) appendl(buf, s, \ + (__builtin_constant_p(s) ? sizeof(s)-1 : strlen(s)) ) +#else +static void append(struct apbuf *buf, const char *s) { + appendl(buf, s, strlen(s)); +} +#endif + +static void appendf(struct apbuf *buf, const char *format, ...) + __printflike(2, 3); +static void appendf(struct apbuf *buf, const char *format, ...) { + char *tmp; + va_list va; + size_t len; + + va_start(va, format); + len = xvasprintf(&tmp, format, va); + va_end(va); + appendl(buf, tmp, len); + free(tmp); +} + +/* Make the specified socket non-blocking. */ +static void nonblock_socket(const int sock) { + int flags = fcntl(sock, F_GETFL); + + if (flags == -1) + err(1, "fcntl(F_GETFL)"); + flags |= O_NONBLOCK; + if (fcntl(sock, F_SETFL, flags) == -1) + err(1, "fcntl() to set O_NONBLOCK"); +} + +/* Split string out of src with range [left:right-1] */ +static char *split_string(const char *src, + const size_t left, const size_t right) { + char *dest; + assert(left <= right); + assert(left < strlen(src)); /* [left means must be smaller */ + assert(right <= strlen(src)); /* right) means can be equal or smaller */ + + dest = xmalloc(right - left + 1); + memcpy(dest, src+left, right-left); + dest[right-left] = '\0'; + return dest; +} + +/* Resolve /./ and /../ in a URL, in-place. + * Returns NULL if the URL is invalid/unsafe, or the original buffer if + * successful. + */ +static char *make_safe_url(char *const url) { + char *src = url, *dst; + #define ends(c) ((c) == '/' || (c) == '\0') + + /* URLs not starting with a slash are illegal. */ + if (*src != '/') + return NULL; + + /* Fast case: skip until first double-slash or dot-dir. */ + for ( ; *src; ++src) { + if (*src == '/') { + if (src[1] == '/') + break; + else if (src[1] == '.') { + if (ends(src[2])) + break; + else if (src[2] == '.' && ends(src[3])) + break; + } + } + } + + /* Copy to dst, while collapsing multi-slashes and handling dot-dirs. */ + dst = src; + while (*src) { + if (*src != '/') + *dst++ = *src++; + else if (*++src == '/') + ; + else if (*src != '.') + *dst++ = '/'; + else if (ends(src[1])) + /* Ignore single-dot component. */ + ++src; + else if (src[1] == '.' && ends(src[2])) { + /* Double-dot component. */ + src += 2; + if (dst == url) + return NULL; /* Illegal URL */ + else + /* Backtrack to previous slash. */ + while (*--dst != '/' && dst > url); + } + else + *dst++ = '/'; + } + + if (dst == url) + ++dst; + *dst = '\0'; + return url; + #undef ends +} + +static void add_forward_mapping(const char * const host, + const char * const target_url) { + forward_map_size++; + forward_map = xrealloc(forward_map, + sizeof(*forward_map) * forward_map_size); + forward_map[forward_map_size - 1].host = host; + forward_map[forward_map_size - 1].target_url = target_url; +} + +/* Associates an extension with a mimetype in the mime_map. Entries are in + * unsorted order. Makes copies of extension and mimetype strings. + */ +static void add_mime_mapping(const char *extension, const char *mimetype) { + size_t i; + assert(strlen(extension) > 0); + assert(strlen(mimetype) > 0); + + /* update longest_ext */ + i = strlen(extension); + if (i > longest_ext) + longest_ext = i; + + /* look through list and replace an existing entry if possible */ + for (i = 0; i < mime_map_size; i++) + if (strcmp(mime_map[i].extension, extension) == 0) { + free(mime_map[i].mimetype); + mime_map[i].mimetype = xstrdup(mimetype); + return; + } + + /* no replacement - add a new entry */ + mime_map_size++; + mime_map = xrealloc(mime_map, + sizeof(struct mime_mapping) * mime_map_size); + mime_map[mime_map_size - 1].extension = xstrdup(extension); + mime_map[mime_map_size - 1].mimetype = xstrdup(mimetype); +} + +/* qsort() the mime_map. The map must be sorted before it can be + * binary-searched. + */ +static int mime_mapping_cmp(const void *a, const void *b) { + return strcmp(((const struct mime_mapping *)a)->extension, + ((const struct mime_mapping *)b)->extension); +} + +static void sort_mime_map(void) { + qsort(mime_map, mime_map_size, sizeof(struct mime_mapping), + mime_mapping_cmp); +} + +/* Parses a mime.types line and adds the parsed data to the mime_map. */ +static void parse_mimetype_line(const char *line) { + unsigned int pad, bound1, lbound, rbound; + + /* parse mimetype */ + for (pad=0; (line[pad] == ' ') || (line[pad] == '\t'); pad++) + ; + if (line[pad] == '\0' || /* empty line */ + line[pad] == '#') /* comment */ + return; + + for (bound1=pad+1; + (line[bound1] != ' ') && + (line[bound1] != '\t'); + bound1++) { + if (line[bound1] == '\0') + return; /* malformed line */ + } + + lbound = bound1; + for (;;) { + char *mimetype, *extension; + + /* find beginning of extension */ + for (; (line[lbound] == ' ') || (line[lbound] == '\t'); lbound++) + ; + if (line[lbound] == '\0') + return; /* end of line */ + + /* find end of extension */ + for (rbound = lbound; + line[rbound] != ' ' && + line[rbound] != '\t' && + line[rbound] != '\0'; + rbound++) + ; + + mimetype = split_string(line, pad, bound1); + extension = split_string(line, lbound, rbound); + add_mime_mapping(extension, mimetype); + free(mimetype); + free(extension); + + if (line[rbound] == '\0') + return; /* end of line */ + else + lbound = rbound + 1; + } +} + +/* Adds contents of default_extension_map[] to mime_map list. The array must + * be NULL terminated. + */ +static void parse_default_extension_map(void) { + size_t i; + + for (i = 0; default_extension_map[i] != NULL; i++) + parse_mimetype_line(default_extension_map[i]); +} + +/* read a line from fp, return its contents in a dynamically allocated buffer, + * not including the line ending. + * + * Handles CR, CRLF and LF line endings, as well as NOEOL correctly. If + * already at EOF, returns NULL. Will err() or errx() in case of + * unexpected file error or running out of memory. + */ +static char *read_line(FILE *fp) { + char *buf; + long startpos, endpos; + size_t linelen, numread; + int c; + + startpos = ftell(fp); + if (startpos == -1) + err(1, "ftell()"); + + /* find end of line (or file) */ + linelen = 0; + for (;;) { + c = fgetc(fp); + if ((c == EOF) || (c == (int)'\n') || (c == (int)'\r')) + break; + linelen++; + } + + /* return NULL on EOF (and empty line) */ + if (linelen == 0 && c == EOF) + return NULL; + + endpos = ftell(fp); + if (endpos == -1) + err(1, "ftell()"); + + /* skip CRLF */ + if ((c == (int)'\r') && (fgetc(fp) == (int)'\n')) + endpos++; + + buf = xmalloc(linelen + 1); + + /* rewind file to where the line stared and load the line */ + if (fseek(fp, startpos, SEEK_SET) == -1) + err(1, "fseek()"); + numread = fread(buf, 1, linelen, fp); + if (numread != linelen) + errx(1, "fread() %zu bytes, expecting %zu bytes", numread, linelen); + + /* terminate buffer */ + buf[linelen] = 0; + + /* advance file pointer over the endline */ + if (fseek(fp, endpos, SEEK_SET) == -1) + err(1, "fseek()"); + + return buf; +} + +/* --------------------------------------------------------------------------- + * Adds contents of specified file to mime_map list. + */ +static void parse_extension_map_file(const char *filename) { + char *buf; + FILE *fp = fopen(filename, "rb"); + + if (fp == NULL) + err(1, "fopen(\"%s\")", filename); + while ((buf = read_line(fp)) != NULL) { + parse_mimetype_line(buf); + free(buf); + } + fclose(fp); +} + +/* Uses the mime_map to determine a Content-Type: for a requested URL. This + * bsearch()es mime_map, so make sure it's sorted first. + */ +static int mime_mapping_cmp_str(const void *a, const void *b) { + return strcmp((const char *)a, + ((const struct mime_mapping *)b)->extension); +} + +static const char *url_content_type(const char *url) { + int period, urllen = (int)strlen(url); + + for (period = urllen - 1; + (period > 0) && (url[period] != '.') && + (urllen - period - 1 <= (int)longest_ext); + period--) + ; + + if ((period >= 0) && (url[period] == '.')) { + struct mime_mapping *result = + bsearch((url + period + 1), mime_map, mime_map_size, + sizeof(struct mime_mapping), mime_mapping_cmp_str); + if (result != NULL) { + assert(strcmp(url + period + 1, result->extension) == 0); + return result->mimetype; + } + } + /* else no period found in the string */ + return default_mimetype; +} + +static const char *get_address_text(const void *addr) { +#ifdef HAVE_INET6 + if (inet6) { + static char text_addr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, (const struct in6_addr *)addr, text_addr, + INET6_ADDRSTRLEN); + return text_addr; + } else +#endif + { + return inet_ntoa(*(const struct in_addr *)addr); + } +} + +/* Initialize the sockin global. This is the socket that we accept + * connections from. + */ +static void init_sockin(void) { + struct sockaddr_in addrin; +#ifdef HAVE_INET6 + struct sockaddr_in6 addrin6; +#endif + socklen_t addrin_len; + int sockopt; + +#ifdef HAVE_INET6 + if (inet6) { + memset(&addrin6, 0, sizeof(addrin6)); + if (inet_pton(AF_INET6, bindaddr ? bindaddr : "::", + &addrin6.sin6_addr) == -1) { + errx(1, "malformed --addr argument"); + } + sockin = socket(PF_INET6, SOCK_STREAM, 0); + } else +#endif + { + memset(&addrin, 0, sizeof(addrin)); + addrin.sin_addr.s_addr = bindaddr ? inet_addr(bindaddr) : INADDR_ANY; + if (addrin.sin_addr.s_addr == (in_addr_t)INADDR_NONE) + errx(1, "malformed --addr argument"); + sockin = socket(PF_INET, SOCK_STREAM, 0); + } + + if (sockin == -1) + err(1, "socket()"); + + /* reuse address */ + sockopt = 1; + if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR, + &sockopt, sizeof(sockopt)) == -1) + err(1, "setsockopt(SO_REUSEADDR)"); + +#if 0 + /* disable Nagle since we buffer everything ourselves */ + sockopt = 1; + if (setsockopt(sockin, IPPROTO_TCP, TCP_NODELAY, + &sockopt, sizeof(sockopt)) == -1) + err(1, "setsockopt(TCP_NODELAY)"); +#endif + +#ifdef TORTURE + /* torture: cripple the kernel-side send buffer so we can only squeeze out + * one byte at a time (this is for debugging) + */ + sockopt = 1; + if (setsockopt(sockin, SOL_SOCKET, SO_SNDBUF, + &sockopt, sizeof(sockopt)) == -1) + err(1, "setsockopt(SO_SNDBUF)"); +#endif + + /* bind socket */ +#ifdef HAVE_INET6 + if (inet6) { + addrin6.sin6_family = AF_INET6; + addrin6.sin6_port = htons(bindport); + if (bind(sockin, (struct sockaddr *)&addrin6, + sizeof(struct sockaddr_in6)) == -1) + err(1, "bind(port %u)", bindport); + + addrin_len = sizeof(addrin6); + if (getsockname(sockin, (struct sockaddr *)&addrin6, &addrin_len) == -1) + err(1, "getsockname()"); + printf("listening on: http://[%s]:%u/\n", + get_address_text(&addrin6.sin6_addr), bindport); + } else +#endif + { + addrin.sin_family = (u_char)PF_INET; + addrin.sin_port = htons(bindport); + if (bind(sockin, (struct sockaddr *)&addrin, + sizeof(struct sockaddr_in)) == -1) + err(1, "bind(port %u)", bindport); + addrin_len = sizeof(addrin); + if (getsockname(sockin, (struct sockaddr *)&addrin, &addrin_len) == -1) + err(1, "getsockname()"); + printf("listening on: http://%s:%u/\n", + get_address_text(&addrin.sin_addr), bindport); + } + + /* listen on socket */ + if (listen(sockin, max_connections) == -1) + err(1, "listen()"); + + /* enable acceptfilter (this is only available on FreeBSD) */ + if (want_accf) { +#if defined(__FreeBSD__) + struct accept_filter_arg filt = {"httpready", ""}; + if (setsockopt(sockin, SOL_SOCKET, SO_ACCEPTFILTER, + &filt, sizeof(filt)) == -1) + fprintf(stderr, "cannot enable acceptfilter: %s\n", + strerror(errno)); + else + printf("enabled acceptfilter\n"); +#else + printf("this platform doesn't support acceptfilter\n"); +#endif + } +} + +static void usage(const char *argv0) { + printf("usage:\t%s /path/to/wwwroot [flags]\n\n", argv0); + printf("flags:\t--port number (default: %u, or 80 if running as root)\n" + "\t\tSpecifies which port to listen on for connections.\n" + "\t\tPass 0 to let the system choose any free port for you.\n\n", bindport); + printf("\t--addr ip (default: all)\n" + "\t\tIf multiple interfaces are present, specifies\n" + "\t\twhich one to bind the listening port to.\n\n"); + printf("\t--maxconn number (default: system maximum)\n" + "\t\tSpecifies how many concurrent connections to accept.\n\n"); + printf("\t--log filename (default: stdout)\n" + "\t\tSpecifies which file to append the request log to.\n\n"); + printf("\t--syslog\n" + "\t\tUse syslog for request log.\n\n"); + printf("\t--chroot (default: don't chroot)\n" + "\t\tLocks server into wwwroot directory for added security.\n\n"); + printf("\t--daemon (default: don't daemonize)\n" + "\t\tDetach from the controlling terminal and run in the background.\n\n"); + printf("\t--index filename (default: %s)\n" + "\t\tDefault file to serve when a directory is requested.\n\n", + index_name); + printf("\t--no-listing\n" + "\t\tDo not serve listing if directory is requested.\n\n"); + printf("\t--mimetypes filename (optional)\n" + "\t\tParses specified file for extension-MIME associations.\n\n"); + printf("\t--default-mimetype string (optional, default: %s)\n" + "\t\tFiles with unknown extensions are served as this mimetype.\n\n", + octet_stream); + printf("\t--uid uid/uname, --gid gid/gname (default: don't privdrop)\n" + "\t\tDrops privileges to given uid:gid after initialization.\n\n"); + printf("\t--pidfile filename (default: no pidfile)\n" + "\t\tWrite PID to the specified file. Note that if you are\n" + "\t\tusing --chroot, then the pidfile must be relative to,\n" + "\t\tand inside the wwwroot.\n\n"); + printf("\t--no-keepalive\n" + "\t\tDisables HTTP Keep-Alive functionality.\n\n"); +#ifdef __FreeBSD__ + printf("\t--accf (default: don't use acceptfilter)\n" + "\t\tUse acceptfilter. Needs the accf_http module loaded.\n\n"); +#endif + printf("\t--forward host url (default: don't forward)\n" + "\t\tWeb forward (301 redirect).\n" + "\t\tRequests to the host are redirected to the corresponding url.\n" + "\t\tThe option may be specified multiple times, in which case\n" + "\t\tthe host is matched in order of appearance.\n\n"); + printf("\t--forward-all url (default: don't forward)\n" + "\t\tWeb forward (301 redirect).\n" + "\t\tAll requests are redirected to the corresponding url.\n\n"); + printf("\t--no-server-id\n" + "\t\tDon't identify the server type in headers\n" + "\t\tor directory listings.\n\n"); + printf("\t--timeout secs (default: %d)\n" + "\t\tIf a connection is idle for more than this many seconds,\n" + "\t\tit will be closed. Set to zero to disable timeouts.\n\n", + timeout_secs); + printf("\t--auth username:password\n" + "\t\tEnable basic authentication.\n\n"); +#ifdef HAVE_INET6 + printf("\t--ipv6\n" + "\t\tListen on IPv6 address.\n\n"); +#else + printf("\t(This binary was built without IPv6 support: -DNO_IPV6)\n\n"); +#endif +} + +static char *base64_encode(char *str) { + const char base64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/'}; + + int input_length = strlen(str); + int output_length = 4 * ((input_length + 2) / 3); + + char *encoded_data = malloc(output_length+1); + if (encoded_data == NULL) return NULL; + + int i; + int j; + for (i = 0, j = 0; i < input_length;) { + uint32_t octet_a = i < input_length ? (unsigned char)str[i++] : 0; + uint32_t octet_b = i < input_length ? (unsigned char)str[i++] : 0; + uint32_t octet_c = i < input_length ? (unsigned char)str[i++] : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + encoded_data[j++] = base64_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = base64_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = base64_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = base64_table[(triple >> 0 * 6) & 0x3F]; + } + + const int mod_table[] = {0, 2, 1}; + for (i = 0; i < mod_table[input_length % 3]; i++) + encoded_data[output_length - 1 - i] = '='; + encoded_data[output_length] = '\0'; + + return encoded_data; +} + +/* Returns 1 if string is a number, 0 otherwise. Set num to NULL if + * disinterested in value. + */ +static int str_to_num(const char *str, long long *num) { + char *endptr; + long long n; + + errno = 0; + n = strtoll(str, &endptr, 10); + if (*endptr != '\0') + return 0; + if (n == LLONG_MIN && errno == ERANGE) + return 0; + if (n == LLONG_MAX && errno == ERANGE) + return 0; + if (num != NULL) + *num = n; + return 1; +} + +/* Returns a valid number or dies. */ +static long long xstr_to_num(const char *str) { + long long ret; + + if (!str_to_num(str, &ret)) { + errx(1, "number \"%s\" is invalid", str); + } + return ret; +} + +static void parse_commandline(const int argc, char *argv[]) { + int i; + size_t len; + + if ((argc < 2) || (argc == 2 && strcmp(argv[1], "--help") == 0)) { + usage(argv[0]); /* no wwwroot given */ + exit(EXIT_SUCCESS); + } + + if (getuid() == 0) + bindport = 80; + + wwwroot = xstrdup(argv[1]); + /* Strip ending slash. */ + len = strlen(wwwroot); + if (len > 0) + if (wwwroot[len - 1] == '/') + wwwroot[len - 1] = '\0'; + + /* walk through the remainder of the arguments (if any) */ + for (i = 2; i < argc; i++) { + if (strcmp(argv[i], "--port") == 0) { + if (++i >= argc) + errx(1, "missing number after --port"); + bindport = (uint16_t)xstr_to_num(argv[i]); + } + else if (strcmp(argv[i], "--addr") == 0) { + if (++i >= argc) + errx(1, "missing ip after --addr"); + bindaddr = argv[i]; + } + else if (strcmp(argv[i], "--maxconn") == 0) { + if (++i >= argc) + errx(1, "missing number after --maxconn"); + max_connections = (int)xstr_to_num(argv[i]); + } + else if (strcmp(argv[i], "--log") == 0) { + if (++i >= argc) + errx(1, "missing filename after --log"); + logfile_name = argv[i]; + } + else if (strcmp(argv[i], "--chroot") == 0) { + want_chroot = 1; + } + else if (strcmp(argv[i], "--daemon") == 0) { + want_daemon = 1; + } + else if (strcmp(argv[i], "--index") == 0) { + if (++i >= argc) + errx(1, "missing filename after --index"); + index_name = argv[i]; + } + else if (strcmp(argv[i], "--no-listing") == 0) { + no_listing = 1; + } + else if (strcmp(argv[i], "--mimetypes") == 0) { + if (++i >= argc) + errx(1, "missing filename after --mimetypes"); + parse_extension_map_file(argv[i]); + } + else if (strcmp(argv[i], "--default-mimetype") == 0) { + if (++i >= argc) + errx(1, "missing string after --default-mimetype"); + default_mimetype = argv[i]; + } + else if (strcmp(argv[i], "--uid") == 0) { + struct passwd *p; + if (++i >= argc) + errx(1, "missing uid after --uid"); + p = getpwnam(argv[i]); + if (!p) { + p = getpwuid((uid_t)xstr_to_num(argv[i])); + } + if (!p) + errx(1, "no such uid: `%s'", argv[i]); + drop_uid = p->pw_uid; + } + else if (strcmp(argv[i], "--gid") == 0) { + struct group *g; + if (++i >= argc) + errx(1, "missing gid after --gid"); + g = getgrnam(argv[i]); + if (!g) { + g = getgrgid((gid_t)xstr_to_num(argv[i])); + } + if (!g) { + errx(1, "no such gid: `%s'", argv[i]); + } + drop_gid = g->gr_gid; + } + else if (strcmp(argv[i], "--pidfile") == 0) { + if (++i >= argc) + errx(1, "missing filename after --pidfile"); + pidfile_name = argv[i]; + } + else if (strcmp(argv[i], "--no-keepalive") == 0) { + want_keepalive = 0; + } + else if (strcmp(argv[i], "--accf") == 0) { + want_accf = 1; + } + else if (strcmp(argv[i], "--syslog") == 0) { + syslog_enabled = 1; + } + else if (strcmp(argv[i], "--forward") == 0) { + const char *host, *url; + if (++i >= argc) + errx(1, "missing host after --forward"); + host = argv[i]; + if (++i >= argc) + errx(1, "missing url after --forward"); + url = argv[i]; + add_forward_mapping(host, url); + } + else if (strcmp(argv[i], "--forward-all") == 0) { + if (++i >= argc) + errx(1, "missing url after --forward-all"); + forward_all_url = argv[i]; + } + else if (strcmp(argv[i], "--no-server-id") == 0) { + want_server_id = 0; + } + else if (strcmp(argv[i], "--timeout") == 0) { + if (++i >= argc) + errx(1, "missing number after --timeout"); + timeout_secs = (int)xstr_to_num(argv[i]); + } + else if (strcmp(argv[i], "--auth") == 0) { + if (++i >= argc || strchr(argv[i], ':') == NULL) + errx(1, "missing 'user:pass' after --auth"); + + char *key = base64_encode(argv[i]); + xasprintf(&auth_key, "Basic %s", key); + free(key); + } +#ifdef HAVE_INET6 + else if (strcmp(argv[i], "--ipv6") == 0) { + inet6 = 1; + } +#endif + else + errx(1, "unknown argument `%s'", argv[i]); + } +} + +/* Allocate and initialize an empty connection. */ +static struct connection *new_connection(void) { + struct connection *conn = xmalloc(sizeof(struct connection)); + + conn->socket = -1; + memset(&conn->client, 0, sizeof(conn->client)); + conn->last_active = now; + conn->request = NULL; + conn->request_length = 0; + conn->method = NULL; + conn->url = NULL; + conn->referer = NULL; + conn->user_agent = NULL; + conn->authorization = NULL; + conn->range_begin = 0; + conn->range_end = 0; + conn->range_begin_given = 0; + conn->range_end_given = 0; + conn->header = NULL; + conn->header_length = 0; + conn->header_sent = 0; + conn->header_dont_free = 0; + conn->header_only = 0; + conn->http_code = 0; + conn->conn_close = 1; + conn->reply = NULL; + conn->reply_dont_free = 0; + conn->reply_fd = -1; + conn->reply_start = 0; + conn->reply_length = 0; + conn->reply_sent = 0; + conn->total_sent = 0; + + /* Make it harmless so it gets garbage-collected if it should, for some + * reason, fail to be correctly filled out. + */ + conn->state = DONE; + + return conn; +} + +/* Accept a connection from sockin and add it to the connection queue. */ +static void accept_connection(void) { + struct sockaddr_in addrin; +#ifdef HAVE_INET6 + struct sockaddr_in6 addrin6; +#endif + socklen_t sin_size; + struct connection *conn; + int fd; + +#ifdef HAVE_INET6 + if (inet6) { + sin_size = sizeof(addrin6); + memset(&addrin6, 0, sin_size); + fd = accept(sockin, (struct sockaddr *)&addrin6, &sin_size); + } else +#endif + { + sin_size = sizeof(addrin); + memset(&addrin, 0, sin_size); + fd = accept(sockin, (struct sockaddr *)&addrin, &sin_size); + } + + if (fd == -1) { + /* Failed to accept, but try to keep serving existing connections. */ + if (errno == EMFILE || errno == ENFILE) accepting = 0; + warn("accept()"); + return; + } + + /* Allocate and initialize struct connection. */ + conn = new_connection(); + conn->socket = fd; + nonblock_socket(conn->socket); + conn->state = RECV_REQUEST; + +#ifdef HAVE_INET6 + if (inet6) { + conn->client = addrin6.sin6_addr; + } else +#endif + { + *(in_addr_t *)&conn->client = addrin.sin_addr.s_addr; + } + LIST_INSERT_HEAD(&connlist, conn, entries); + + if (debug) + printf("accepted connection from %s:%u (fd %d)\n", + inet_ntoa(addrin.sin_addr), + ntohs(addrin.sin_port), + conn->socket); + + /* Try to read straight away rather than going through another iteration + * of the select() loop. + */ + poll_recv_request(conn); +} + +/* Should this character be logencoded? + */ +static int needs_logencoding(const unsigned char c) { + return ((c <= 0x1F) || (c >= 0x7F) || (c == '"')); +} + +/* Encode string for logging. + */ +static void logencode(const char *src, char *dest) { + static const char hex[] = "0123456789ABCDEF"; + int i, j; + + for (i = j = 0; src[i] != '\0'; i++) { + if (needs_logencoding((unsigned char)src[i])) { + dest[j++] = '%'; + dest[j++] = hex[(src[i] >> 4) & 0xF]; + dest[j++] = hex[ src[i] & 0xF]; + } + else + dest[j++] = src[i]; + } + dest[j] = '\0'; +} + +/* Format [when] as a CLF date format, stored in the specified buffer. The same + * buffer is returned for convenience. + */ +#define CLF_DATE_LEN 29 /* strlen("[10/Oct/2000:13:55:36 -0700]")+1 */ +static char *clf_date(char *dest, const time_t when) { + time_t when_copy = when; + if (strftime(dest, CLF_DATE_LEN, + "[%d/%b/%Y:%H:%M:%S %z]", localtime(&when_copy)) == 0) + errx(1, "strftime() failed [%s]", dest); + return dest; +} + +/* Add a connection's details to the logfile. */ +static void log_connection(const struct connection *conn) { + char *safe_method, *safe_url, *safe_referer, *safe_user_agent, + dest[CLF_DATE_LEN]; + + if (logfile == NULL) + return; + if (conn->http_code == 0) + return; /* invalid - died in request */ + if (conn->method == NULL) + return; /* invalid - didn't parse - maybe too long */ + +#define make_safe(x) do { \ + if (conn->x) { \ + safe_##x = xmalloc(strlen(conn->x)*3 + 1); \ + logencode(conn->x, safe_##x); \ + } else { \ + safe_##x = NULL; \ + } \ +} while(0) + + make_safe(method); + make_safe(url); + make_safe(referer); + make_safe(user_agent); + +#define use_safe(x) safe_##x ? safe_##x : "" + if (syslog_enabled) { + syslog(LOG_INFO, "%s - - %s \"%s %s HTTP/1.1\" %d %llu \"%s\" \"%s\"\n", + get_address_text(&conn->client), + clf_date(dest, now), + use_safe(method), + use_safe(url), + conn->http_code, + llu(conn->total_sent), + use_safe(referer), + use_safe(user_agent) + ); + } else { + fprintf(logfile, "%s - - %s \"%s %s HTTP/1.1\" %d %llu \"%s\" \"%s\"\n", + get_address_text(&conn->client), + clf_date(dest, now), + use_safe(method), + use_safe(url), + conn->http_code, + llu(conn->total_sent), + use_safe(referer), + use_safe(user_agent) + ); + fflush(logfile); + } +#define free_safe(x) if (safe_##x) free(safe_##x) + + free_safe(method); + free_safe(url); + free_safe(referer); + free_safe(user_agent); + +#undef make_safe +#undef use_safe +#undef free_safe +} + +/* Log a connection, then cleanly deallocate its internals. */ +static void free_connection(struct connection *conn) { + if (debug) printf("free_connection(%d)\n", conn->socket); + log_connection(conn); + if (conn->socket != -1) xclose(conn->socket); + if (conn->request != NULL) free(conn->request); + if (conn->method != NULL) free(conn->method); + if (conn->url != NULL) free(conn->url); + if (conn->referer != NULL) free(conn->referer); + if (conn->user_agent != NULL) free(conn->user_agent); + if (conn->authorization != NULL) free(conn->authorization); + if (conn->header != NULL && !conn->header_dont_free) free(conn->header); + if (conn->reply != NULL && !conn->reply_dont_free) free(conn->reply); + if (conn->reply_fd != -1) xclose(conn->reply_fd); + /* If we ran out of sockets, try to resume accepting. */ + accepting = 1; +} + +/* Recycle a finished connection for HTTP/1.1 Keep-Alive. */ +static void recycle_connection(struct connection *conn) { + int socket_tmp = conn->socket; + if (debug) + printf("recycle_connection(%d)\n", socket_tmp); + conn->socket = -1; /* so free_connection() doesn't close it */ + free_connection(conn); + conn->socket = socket_tmp; + + /* don't reset conn->client */ + conn->request = NULL; + conn->request_length = 0; + conn->method = NULL; + conn->url = NULL; + conn->referer = NULL; + conn->user_agent = NULL; + conn->authorization = NULL; + conn->range_begin = 0; + conn->range_end = 0; + conn->range_begin_given = 0; + conn->range_end_given = 0; + conn->header = NULL; + conn->header_length = 0; + conn->header_sent = 0; + conn->header_dont_free = 0; + conn->header_only = 0; + conn->http_code = 0; + conn->conn_close = 1; + conn->reply = NULL; + conn->reply_dont_free = 0; + conn->reply_fd = -1; + conn->reply_start = 0; + conn->reply_length = 0; + conn->reply_sent = 0; + conn->total_sent = 0; + + conn->state = RECV_REQUEST; /* ready for another */ +} + +/* Uppercasify all characters in a string of given length. */ +static void strntoupper(char *str, const size_t length) { + size_t i; + + for (i = 0; i < length; i++) + str[i] = (char)toupper(str[i]); +} + +/* If a connection has been idle for more than timeout_secs, it will be + * marked as DONE and killed off in httpd_poll(). + */ +static void poll_check_timeout(struct connection *conn) { + if (timeout_secs > 0) { + if (now - conn->last_active >= timeout_secs) { + if (debug) + printf("poll_check_timeout(%d) closing connection\n", + conn->socket); + conn->conn_close = 1; + conn->state = DONE; + } + } +} + +/* Format [when] as an RFC1123 date, stored in the specified buffer. The same + * buffer is returned for convenience. + */ +#define DATE_LEN 30 /* strlen("Fri, 28 Feb 2003 00:02:08 GMT")+1 */ +static char *rfc1123_date(char *dest, const time_t when) { + time_t when_copy = when; + if (strftime(dest, DATE_LEN, + "%a, %d %b %Y %H:%M:%S GMT", gmtime(&when_copy)) == 0) + errx(1, "strftime() failed [%s]", dest); + return dest; +} + +/* Decode URL by converting %XX (where XX are hexadecimal digits) to the + * character it represents. Don't forget to free the return value. + */ +static char *urldecode(const char *url) { + size_t i, pos, len = strlen(url); + char *out = xmalloc(len+1); + + for (i = 0, pos = 0; i < len; i++) { + if ((url[i] == '%') && (i+2 < len) && + isxdigit(url[i+1]) && isxdigit(url[i+2])) { + /* decode %XX */ +#define HEX_TO_DIGIT(hex) ( \ + ((hex) >= 'A' && (hex) <= 'F') ? ((hex)-'A'+10): \ + ((hex) >= 'a' && (hex) <= 'f') ? ((hex)-'a'+10): \ + ((hex)-'0') ) + + out[pos++] = HEX_TO_DIGIT(url[i+1]) * 16 + + HEX_TO_DIGIT(url[i+2]); + i += 2; +#undef HEX_TO_DIGIT + } else { + /* straight copy */ + out[pos++] = url[i]; + } + } + out[pos] = '\0'; + return out; +} + +/* Returns Connection or Keep-Alive header, depending on conn_close. */ +static const char *keep_alive(const struct connection *conn) +{ + return (conn->conn_close ? "Connection: close\r\n" : keep_alive_field); +} + +/* "Generated by " + pkgname + " on " + date + "\n" + * 1234567890123 1234 2 ('\n' and '\0') + */ +static char _generated_on_buf[13 + sizeof(pkgname) - 1 + 4 + DATE_LEN + 2]; +static const char *generated_on(const char date[DATE_LEN]) { + if (!want_server_id) + return ""; + snprintf(_generated_on_buf, sizeof(_generated_on_buf), + "Generated by %s on %s\n", + pkgname, date); + return _generated_on_buf; +} + +/* A default reply for any (erroneous) occasion. */ +static void default_reply(struct connection *conn, + const int errcode, const char *errname, const char *format, ...) + __printflike(4, 5); +static void default_reply(struct connection *conn, + const int errcode, const char *errname, const char *format, ...) { + char *reason, date[DATE_LEN]; + va_list va; + + va_start(va, format); + xvasprintf(&reason, format, va); + va_end(va); + + /* Only really need to calculate the date once. */ + rfc1123_date(date, now); + + conn->reply_length = xasprintf(&(conn->reply), + "%d %s\n" + "

%s

\n" /* errname */ + "%s\n" /* reason */ + "
\n" + "%s" /* generated on */ + "\n", + errcode, errname, errname, reason, generated_on(date)); + free(reason); + + const char auth_header[] = + "WWW-Authenticate: Basic realm=\"User Visible Realm\"\r\n"; + + conn->header_length = xasprintf(&(conn->header), + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n" + "%s" /* server */ + "Accept-Ranges: bytes\r\n" + "%s" /* keep-alive */ + "Content-Length: %llu\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "%s" + "\r\n", + errcode, errname, date, server_hdr, keep_alive(conn), + llu(conn->reply_length), + (auth_key != NULL ? auth_header : "")); + + conn->reply_type = REPLY_GENERATED; + conn->http_code = errcode; + + /* Reset reply_start in case the request set a range. */ + conn->reply_start = 0; +} + +static void redirect(struct connection *conn, const char *format, ...) + __printflike(2, 3); +static void redirect(struct connection *conn, const char *format, ...) { + char *where, date[DATE_LEN]; + va_list va; + + va_start(va, format); + xvasprintf(&where, format, va); + va_end(va); + + /* Only really need to calculate the date once. */ + rfc1123_date(date, now); + + conn->reply_length = xasprintf(&(conn->reply), + "301 Moved Permanently\n" + "

Moved Permanently

\n" + "Moved to: %s\n" /* where x 2 */ + "
\n" + "%s" /* generated on */ + "\n", + where, where, generated_on(date)); + + conn->header_length = xasprintf(&(conn->header), + "HTTP/1.1 301 Moved Permanently\r\n" + "Date: %s\r\n" + "%s" /* server */ + /* "Accept-Ranges: bytes\r\n" - not relevant here */ + "Location: %s\r\n" + "%s" /* keep-alive */ + "Content-Length: %llu\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "\r\n", + date, server_hdr, where, keep_alive(conn), llu(conn->reply_length)); + + free(where); + conn->reply_type = REPLY_GENERATED; + conn->http_code = 301; +} + +/* Parses a single HTTP request field. Returns string from end of [field] to + * first \r, \n or end of request string. Returns NULL if [field] can't be + * matched. + * + * You need to remember to deallocate the result. + * example: parse_field(conn, "Referer: "); + */ +static char *parse_field(const struct connection *conn, const char *field) { + size_t bound1, bound2; + char *pos; + + /* find start */ + pos = strstr(conn->request, field); + if (pos == NULL) + return NULL; + assert(pos >= conn->request); + bound1 = (size_t)(pos - conn->request) + strlen(field); + + /* find end */ + for (bound2 = bound1; + ((bound2 < conn->request_length) && + (conn->request[bound2] != '\r') && + (conn->request[bound2] != '\n')); + bound2++) + ; + + /* copy to buffer */ + return split_string(conn->request, bound1, bound2); +} + +/* Parse a Range: field into range_begin and range_end. Only handles the + * first range if a list is given. Sets range_{begin,end}_given to 1 if + * either part of the range is given. + */ +static void parse_range_field(struct connection *conn) { + char *range; + + range = parse_field(conn, "Range: bytes="); + if (range == NULL) + return; + + do { + size_t bound1, bound2, len; + len = strlen(range); + + /* parse number up to hyphen */ + bound1 = 0; + for (bound2=0; + (bound2 < len) && isdigit((int)range[bound2]); + bound2++) + ; + + if ((bound2 == len) || (range[bound2] != '-')) + break; /* there must be a hyphen here */ + + if (bound1 != bound2) { + conn->range_begin_given = 1; + conn->range_begin = (off_t)strtoll(range+bound1, NULL, 10); + } + + /* parse number after hyphen */ + bound2++; + for (bound1=bound2; + (bound2 < len) && isdigit((int)range[bound2]); + bound2++) + ; + + if ((bound2 != len) && (range[bound2] != ',')) + break; /* must be end of string or a list to be valid */ + + if (bound1 != bound2) { + conn->range_end_given = 1; + conn->range_end = (off_t)strtoll(range+bound1, NULL, 10); + } + } while(0); + free(range); +} + +/* Parse an HTTP request like "GET / HTTP/1.1" to get the method (GET), the + * url (/), the referer (if given) and the user-agent (if given). Remember to + * deallocate all these buffers. The method will be returned in uppercase. + */ +static int parse_request(struct connection *conn) { + size_t bound1, bound2; + char *tmp; + assert(conn->request_length == strlen(conn->request)); + + /* parse method */ + for (bound1 = 0; + (bound1 < conn->request_length) && + (conn->request[bound1] != ' '); + bound1++) + ; + + conn->method = split_string(conn->request, 0, bound1); + strntoupper(conn->method, bound1); + + /* parse url */ + for (; + (bound1 < conn->request_length) && + (conn->request[bound1] == ' '); + bound1++) + ; + + if (bound1 == conn->request_length) + return 0; /* fail */ + + for (bound2 = bound1 + 1; + (bound2 < conn->request_length) && + (conn->request[bound2] != ' ') && + (conn->request[bound2] != '\r') && + (conn->request[bound2] != '\n'); + bound2++) + ; + + conn->url = split_string(conn->request, bound1, bound2); + + /* parse protocol to determine conn_close */ + if (conn->request[bound2] == ' ') { + char *proto; + for (bound1 = bound2; + (bound1 < conn->request_length) && + (conn->request[bound1] == ' '); + bound1++) + ; + + for (bound2 = bound1 + 1; + (bound2 < conn->request_length) && + (conn->request[bound2] != ' ') && + (conn->request[bound2] != '\r'); + bound2++) + ; + + proto = split_string(conn->request, bound1, bound2); + if (strcasecmp(proto, "HTTP/1.1") == 0) + conn->conn_close = 0; + free(proto); + } + + /* parse connection field */ + tmp = parse_field(conn, "Connection: "); + if (tmp != NULL) { + if (strcasecmp(tmp, "close") == 0) + conn->conn_close = 1; + else if (strcasecmp(tmp, "keep-alive") == 0) + conn->conn_close = 0; + free(tmp); + } + + /* cmdline flag can be used to deny keep-alive */ + if (!want_keepalive) + conn->conn_close = 1; + + /* parse important fields */ + conn->referer = parse_field(conn, "Referer: "); + conn->user_agent = parse_field(conn, "User-Agent: "); + conn->authorization = parse_field(conn, "Authorization: "); + parse_range_field(conn); + return 1; +} + +static int file_exists(const char *path) { + struct stat filestat; + if ((stat(path, &filestat) == -1) && (errno == ENOENT)) + return 0; + else + return 1; +} + +struct dlent { + char *name; + int is_dir; + off_t size; +}; + +static int dlent_cmp(const void *a, const void *b) { + return strcmp((*((const struct dlent * const *)a))->name, + (*((const struct dlent * const *)b))->name); +} + +/* Make sorted list of files in a directory. Returns number of entries, or -1 + * if error occurs. + */ +static ssize_t make_sorted_dirlist(const char *path, struct dlent ***output) { + DIR *dir; + struct dirent *ent; + size_t entries = 0; + size_t pool = 128; + char *currname; + struct dlent **list = NULL; + + dir = opendir(path); + if (dir == NULL) + return -1; + + currname = xmalloc(strlen(path) + MAXNAMLEN + 1); + list = xmalloc(sizeof(struct dlent*) * pool); + + /* construct list */ + while ((ent = readdir(dir)) != NULL) { + struct stat s; + + if ((ent->d_name[0] == '.') && (ent->d_name[1] == '\0')) + continue; /* skip "." */ + assert(strlen(ent->d_name) <= MAXNAMLEN); + sprintf(currname, "%s%s", path, ent->d_name); + if (stat(currname, &s) == -1) + continue; /* skip un-stat-able files */ + if (entries == pool) { + pool *= 2; + list = xrealloc(list, sizeof(struct dlent*) * pool); + } + list[entries] = xmalloc(sizeof(struct dlent)); + list[entries]->name = xstrdup(ent->d_name); + list[entries]->is_dir = S_ISDIR(s.st_mode); + list[entries]->size = s.st_size; + entries++; + } + closedir(dir); + free(currname); + qsort(list, entries, sizeof(struct dlent*), dlent_cmp); + *output = list; + return (ssize_t)entries; +} + +/* Cleanly deallocate a sorted list of directory files. */ +static void cleanup_sorted_dirlist(struct dlent **list, const ssize_t size) { + ssize_t i; + + for (i = 0; i < size; i++) { + free(list[i]->name); + free(list[i]); + } +} + +/* Is this an unreserved character according to + * https://tools.ietf.org/html/rfc3986#section-2.3 + */ +static int is_unreserved(const unsigned char c) { + if (c >= 'a' && c <= 'z') return 1; + if (c >= 'A' && c <= 'Z') return 1; + if (c >= '0' && c <= '9') return 1; + switch (c) { + case '-': + case '.': + case '_': + case '~': + return 1; + } + return 0; +} + +/* Encode string to be an RFC3986-compliant URL part. + * Contributed by nf. + */ +static void urlencode(const char *src, char *dest) { + static const char hex[] = "0123456789ABCDEF"; + int i, j; + + for (i = j = 0; src[i] != '\0'; i++) { + if (!is_unreserved((unsigned char)src[i])) { + dest[j++] = '%'; + dest[j++] = hex[(src[i] >> 4) & 0xF]; + dest[j++] = hex[ src[i] & 0xF]; + } + else + dest[j++] = src[i]; + } + dest[j] = '\0'; +} + +/* Escape < > & ' " into HTML entities. */ +static void append_escaped(struct apbuf *dst, const char *src) { + int pos = 0; + while (src[pos] != '\0') { + switch (src[pos]) { + case '<': + append(dst, "<"); + break; + case '>': + append(dst, ">"); + break; + case '&': + append(dst, "&"); + break; + case '\'': + append(dst, "'"); + break; + case '"': + append(dst, """); + break; + default: + appendl(dst, src+pos, 1); + } + pos++; + } +} + +static void generate_dir_listing(struct connection *conn, const char *path, + const char *decoded_url) { + char date[DATE_LEN], *spaces; + struct dlent **list; + ssize_t listsize; + size_t maxlen = 2; /* There has to be ".." */ + int i; + struct apbuf *listing; + + listsize = make_sorted_dirlist(path, &list); + if (listsize == -1) { + default_reply(conn, 500, "Internal Server Error", + "Couldn't list directory: %s", strerror(errno)); + return; + } + + for (i=0; iname); + if (maxlen < tmp) + maxlen = tmp; + } + + listing = make_apbuf(); + append(listing, "\n\n"); + append_escaped(listing, decoded_url); + append(listing, + "\n" + "\n" + "\n\n

"); + append_escaped(listing, decoded_url); + append(listing, "

\n
\n");
+
+    spaces = xmalloc(maxlen);
+    memset(spaces, ' ', maxlen);
+
+    for (i=0; iname, safe_url);
+
+        append(listing, "");
+        append_escaped(listing, list[i]->name);
+        append(listing, "");
+
+        if (list[i]->is_dir)
+            append(listing, "/\n");
+        else {
+            appendl(listing, spaces, maxlen-strlen(list[i]->name));
+            appendf(listing, "%10llu\n", llu(list[i]->size));
+        }
+    }
+
+    cleanup_sorted_dirlist(list, listsize);
+    free(list);
+    free(spaces);
+
+    append(listing,
+     "
\n" + "
\n"); + + rfc1123_date(date, now); + append(listing, generated_on(date)); + append(listing, "\n\n"); + + conn->reply = listing->str; + conn->reply_length = (off_t)listing->length; + free(listing); /* don't free inside of listing */ + + conn->header_length = xasprintf(&(conn->header), + "HTTP/1.1 200 OK\r\n" + "Date: %s\r\n" + "%s" /* server */ + "Accept-Ranges: bytes\r\n" + "%s" /* keep-alive */ + "Content-Length: %llu\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "\r\n", + date, server_hdr, keep_alive(conn), llu(conn->reply_length)); + + conn->reply_type = REPLY_GENERATED; + conn->http_code = 200; +} + +/* Process a GET/HEAD request. */ +static void process_get(struct connection *conn) { + char *decoded_url, *end, *target, *if_mod_since; + char date[DATE_LEN], lastmod[DATE_LEN]; + const char *mimetype = NULL; + const char *forward_to = NULL; + struct stat filestat; + + /* strip out query params */ + if ((end = strchr(conn->url, '?')) != NULL) + *end = '\0'; + + /* work out path of file being requested */ + decoded_url = urldecode(conn->url); + + /* make sure it's safe */ + if (make_safe_url(decoded_url) == NULL) { + default_reply(conn, 400, "Bad Request", + "You requested an invalid URL."); + free(decoded_url); + return; + } + + /* test the host against web forward options */ + if (forward_map) { + char *host = parse_field(conn, "Host: "); + if (host) { + size_t i; + if (debug) + printf("host=\"%s\"\n", host); + for (i = 0; i < forward_map_size; i++) { + if (strcasecmp(forward_map[i].host, host) == 0) { + forward_to = forward_map[i].target_url; + break; + } + } + free(host); + } + } + if (!forward_to) { + forward_to = forward_all_url; + } + if (forward_to) { + redirect(conn, "%s%s", forward_to, decoded_url); + free(decoded_url); + return; + } + + /* does it end in a slash? serve up url/index_name */ + if (decoded_url[strlen(decoded_url)-1] == '/') { + xasprintf(&target, "%s%s%s", wwwroot, decoded_url, index_name); + if (!file_exists(target)) { + free(target); + if (no_listing) { + free(decoded_url); + /* Return 404 instead of 403 to make --no-listing + * indistinguishable from the directory not existing. + * i.e.: Don't leak information. + */ + default_reply(conn, 404, "Not Found", + "The URL you requested was not found."); + return; + } + xasprintf(&target, "%s%s", wwwroot, decoded_url); + generate_dir_listing(conn, target, decoded_url); + free(target); + free(decoded_url); + return; + } + mimetype = url_content_type(index_name); + } + else { + /* points to a file */ + xasprintf(&target, "%s%s", wwwroot, decoded_url); + mimetype = url_content_type(decoded_url); + } + free(decoded_url); + if (debug) + printf("url=\"%s\", target=\"%s\", content-type=\"%s\"\n", + conn->url, target, mimetype); + + /* open file */ + conn->reply_fd = open(target, O_RDONLY | O_NONBLOCK); + free(target); + + if (conn->reply_fd == -1) { + /* open() failed */ + if (errno == EACCES) + default_reply(conn, 403, "Forbidden", + "You don't have permission to access this URL."); + else if (errno == ENOENT) + default_reply(conn, 404, "Not Found", + "The URL you requested was not found."); + else + default_reply(conn, 500, "Internal Server Error", + "The URL you requested cannot be returned: %s.", + strerror(errno)); + + return; + } + + /* stat the file */ + if (fstat(conn->reply_fd, &filestat) == -1) { + default_reply(conn, 500, "Internal Server Error", + "fstat() failed: %s.", strerror(errno)); + return; + } + + /* make sure it's a regular file */ + if (S_ISDIR(filestat.st_mode)) { + redirect(conn, "%s/", conn->url); + return; + } + else if (!S_ISREG(filestat.st_mode)) { + default_reply(conn, 403, "Forbidden", "Not a regular file."); + return; + } + + conn->reply_type = REPLY_FROMFILE; + rfc1123_date(lastmod, filestat.st_mtime); + + /* check for If-Modified-Since, may not have to send */ + if_mod_since = parse_field(conn, "If-Modified-Since: "); + if ((if_mod_since != NULL) && + (strcmp(if_mod_since, lastmod) == 0)) { + if (debug) + printf("not modified since %s\n", if_mod_since); + conn->http_code = 304; + conn->header_length = xasprintf(&(conn->header), + "HTTP/1.1 304 Not Modified\r\n" + "Date: %s\r\n" + "%s" /* server */ + "Accept-Ranges: bytes\r\n" + "%s" /* keep-alive */ + "\r\n", + rfc1123_date(date, now), server_hdr, keep_alive(conn)); + conn->reply_length = 0; + conn->reply_type = REPLY_GENERATED; + conn->header_only = 1; + + free(if_mod_since); + return; + } + free(if_mod_since); + + if (conn->range_begin_given || conn->range_end_given) { + off_t from, to; + + if (conn->range_begin_given && conn->range_end_given) { + /* 100-200 */ + from = conn->range_begin; + to = conn->range_end; + + /* clamp end to filestat.st_size-1 */ + if (to > (filestat.st_size - 1)) + to = filestat.st_size - 1; + } + else if (conn->range_begin_given && !conn->range_end_given) { + /* 100- :: yields 100 to end */ + from = conn->range_begin; + to = filestat.st_size - 1; + } + else if (!conn->range_begin_given && conn->range_end_given) { + /* -200 :: yields last 200 */ + to = filestat.st_size - 1; + from = to - conn->range_end + 1; + + /* clamp start */ + if (from < 0) + from = 0; + } + else + errx(1, "internal error - from/to mismatch"); + + if (from >= filestat.st_size) { + default_reply(conn, 416, "Requested Range Not Satisfiable", + "You requested a range outside of the file."); + return; + } + + if (to < from) { + default_reply(conn, 416, "Requested Range Not Satisfiable", + "You requested a backward range."); + return; + } + + conn->reply_start = from; + conn->reply_length = to - from + 1; + + conn->header_length = xasprintf(&(conn->header), + "HTTP/1.1 206 Partial Content\r\n" + "Date: %s\r\n" + "%s" /* server */ + "Accept-Ranges: bytes\r\n" + "%s" /* keep-alive */ + "Content-Length: %llu\r\n" + "Content-Range: bytes %llu-%llu/%llu\r\n" + "Content-Type: %s\r\n" + "Last-Modified: %s\r\n" + "\r\n" + , + rfc1123_date(date, now), server_hdr, keep_alive(conn), + llu(conn->reply_length), llu(from), llu(to), + llu(filestat.st_size), mimetype, lastmod + ); + conn->http_code = 206; + if (debug) + printf("sending %llu-%llu/%llu\n", + llu(from), llu(to), llu(filestat.st_size)); + } + else { + /* no range stuff */ + conn->reply_length = filestat.st_size; + conn->header_length = xasprintf(&(conn->header), + "HTTP/1.1 200 OK\r\n" + "Date: %s\r\n" + "%s" /* server */ + "Accept-Ranges: bytes\r\n" + "%s" /* keep-alive */ + "Content-Length: %llu\r\n" + "Content-Type: %s\r\n" + "Last-Modified: %s\r\n" + "\r\n" + , + rfc1123_date(date, now), server_hdr, keep_alive(conn), + llu(conn->reply_length), mimetype, lastmod + ); + conn->http_code = 200; + } +} + +/* Process a request: build the header and reply, advance state. */ +static void process_request(struct connection *conn) { + num_requests++; + + if (!parse_request(conn)) { + default_reply(conn, 400, "Bad Request", + "You sent a request that the server couldn't understand."); + } + /* fail if: (auth_enabled) AND (client supplied invalid credentials) */ + else if (auth_key != NULL && + (conn->authorization == NULL || + strcmp(conn->authorization, auth_key))) + { + default_reply(conn, 401, "Unauthorized", + "Access denied due to invalid credentials."); + } + else if (strcmp(conn->method, "GET") == 0) { + process_get(conn); + } + else if (strcmp(conn->method, "HEAD") == 0) { + process_get(conn); + conn->header_only = 1; + } + else { + default_reply(conn, 501, "Not Implemented", + "The method you specified is not implemented."); + } + + /* advance state */ + conn->state = SEND_HEADER; + + /* request not needed anymore */ + free(conn->request); + conn->request = NULL; /* important: don't free it again later */ +} + +/* Receiving request. */ +static void poll_recv_request(struct connection *conn) { + char buf[1<<15]; + ssize_t recvd; + + assert(conn->state == RECV_REQUEST); + recvd = recv(conn->socket, buf, sizeof(buf), 0); + if (debug) + printf("poll_recv_request(%d) got %d bytes\n", + conn->socket, (int)recvd); + if (recvd < 1) { + if (recvd == -1) { + if (errno == EAGAIN) { + if (debug) printf("poll_recv_request would have blocked\n"); + return; + } + if (debug) printf("recv(%d) error: %s\n", + conn->socket, strerror(errno)); + } + conn->conn_close = 1; + conn->state = DONE; + return; + } + conn->last_active = now; + + /* append to conn->request */ + assert(recvd > 0); + conn->request = xrealloc( + conn->request, conn->request_length + (size_t)recvd + 1); + memcpy(conn->request+conn->request_length, buf, (size_t)recvd); + conn->request_length += (size_t)recvd; + conn->request[conn->request_length] = 0; + total_in += (size_t)recvd; + + /* process request if we have all of it */ + if ((conn->request_length > 2) && + (memcmp(conn->request+conn->request_length-2, "\n\n", 2) == 0)) + process_request(conn); + else if ((conn->request_length > 4) && + (memcmp(conn->request+conn->request_length-4, "\r\n\r\n", 4) == 0)) + process_request(conn); + + /* die if it's too large */ + if (conn->request_length > MAX_REQUEST_LENGTH) { + default_reply(conn, 413, "Request Entity Too Large", + "Your request was dropped because it was too long."); + conn->state = SEND_HEADER; + } + + /* if we've moved on to the next state, try to send right away, instead of + * going through another iteration of the select() loop. + */ + if (conn->state == SEND_HEADER) + poll_send_header(conn); +} + +/* Sending header. Assumes conn->header is not NULL. */ +static void poll_send_header(struct connection *conn) { + ssize_t sent; + + assert(conn->state == SEND_HEADER); + assert(conn->header_length == strlen(conn->header)); + + sent = send(conn->socket, + conn->header + conn->header_sent, + conn->header_length - conn->header_sent, + 0); + conn->last_active = now; + if (debug) + printf("poll_send_header(%d) sent %d bytes\n", + conn->socket, (int)sent); + + /* handle any errors (-1) or closure (0) in send() */ + if (sent < 1) { + if ((sent == -1) && (errno == EAGAIN)) { + if (debug) printf("poll_send_header would have blocked\n"); + return; + } + if (debug && (sent == -1)) + printf("send(%d) error: %s\n", conn->socket, strerror(errno)); + conn->conn_close = 1; + conn->state = DONE; + return; + } + assert(sent > 0); + conn->header_sent += (size_t)sent; + conn->total_sent += (size_t)sent; + total_out += (size_t)sent; + + /* check if we're done sending header */ + if (conn->header_sent == conn->header_length) { + if (conn->header_only) + conn->state = DONE; + else { + conn->state = SEND_REPLY; + /* go straight on to body, don't go through another iteration of + * the select() loop. + */ + poll_send_reply(conn); + } + } +} + +/* Send chunk on socket from FILE *fp, starting at and of size + * . Use sendfile() if possible since it's zero-copy on some platforms. + * Returns the number of bytes sent, 0 on closure, -1 if send() failed, -2 if + * read error. + */ +static ssize_t send_from_file(const int s, const int fd, + off_t ofs, size_t size) { +#ifdef __FreeBSD__ + off_t sent; + int ret = sendfile(fd, s, ofs, size, NULL, &sent, 0); + + /* It is possible for sendfile to send zero bytes due to a blocking + * condition. Handle this correctly. + */ + if (ret == -1) + if (errno == EAGAIN) + if (sent == 0) + return -1; + else + return sent; + else + return -1; + else + return size; +#else +#if defined(__linux) || defined(__sun__) + /* Limit truly ridiculous (LARGEFILE) requests. */ + if (size > 1<<20) + size = 1<<20; + return sendfile(s, fd, &ofs, size); +#else + /* Fake sendfile() with read(). */ +# ifndef min +# define min(a,b) ( ((a)<(b)) ? (a) : (b) ) +# endif + char buf[1<<15]; + size_t amount = min(sizeof(buf), size); + ssize_t numread; + + if (lseek(fd, ofs, SEEK_SET) == -1) + err(1, "fseek(%d)", (int)ofs); + numread = read(fd, buf, amount); + if (numread == 0) { + fprintf(stderr, "premature eof on fd %d\n", fd); + return -1; + } + else if (numread == -1) { + fprintf(stderr, "error reading on fd %d: %s", fd, strerror(errno)); + return -1; + } + else if ((size_t)numread != amount) { + fprintf(stderr, "read %zd bytes, expecting %zu bytes on fd %d\n", + numread, amount, fd); + return -1; + } + else + return send(s, buf, amount, 0); +#endif +#endif +} + +/* Sending reply. */ +static void poll_send_reply(struct connection *conn) +{ + ssize_t sent; + /* off_t can be wider than size_t, avoid overflow in send_len */ + const size_t max_size_t = ~((size_t)0); + off_t send_len = conn->reply_length - conn->reply_sent; + if (send_len > max_size_t) send_len = max_size_t; + + assert(conn->state == SEND_REPLY); + assert(!conn->header_only); + if (conn->reply_type == REPLY_GENERATED) { + assert(conn->reply_length >= conn->reply_sent); + sent = send(conn->socket, + conn->reply + conn->reply_start + conn->reply_sent, + (size_t)send_len, 0); + } + else { + errno = 0; + assert(conn->reply_length >= conn->reply_sent); + sent = send_from_file(conn->socket, conn->reply_fd, + conn->reply_start + conn->reply_sent, (size_t)send_len); + if (debug && (sent < 1)) + printf("send_from_file returned %lld (errno=%d %s)\n", + (long long)sent, errno, strerror(errno)); + } + conn->last_active = now; + if (debug) + printf("poll_send_reply(%d) sent %d: %llu+[%llu-%llu] of %llu\n", + conn->socket, (int)sent, llu(conn->reply_start), + llu(conn->reply_sent), llu(conn->reply_sent + sent - 1), + llu(conn->reply_length)); + + /* handle any errors (-1) or closure (0) in send() */ + if (sent < 1) { + if (sent == -1) { + if (errno == EAGAIN) { + if (debug) + printf("poll_send_reply would have blocked\n"); + return; + } + if (debug) + printf("send(%d) error: %s\n", conn->socket, strerror(errno)); + } + else if (sent == 0) { + if (debug) + printf("send(%d) closure\n", conn->socket); + } + conn->conn_close = 1; + conn->state = DONE; + return; + } + conn->reply_sent += sent; + conn->total_sent += (size_t)sent; + total_out += (size_t)sent; + + /* check if we're done sending */ + if (conn->reply_sent == conn->reply_length) + conn->state = DONE; +} + +/* Main loop of the httpd - a select() and then delegation to accept + * connections, handle receiving of requests, and sending of replies. + */ +static void httpd_poll(void) { + fd_set recv_set, send_set; + int max_fd, select_ret; + struct connection *conn, *next; + int bother_with_timeout = 0; + struct timeval timeout, t0, t1; + + timeout.tv_sec = timeout_secs; + timeout.tv_usec = 0; + + FD_ZERO(&recv_set); + FD_ZERO(&send_set); + max_fd = 0; + + /* set recv/send fd_sets */ +#define MAX_FD_SET(sock, fdset) do { FD_SET(sock,fdset); \ + max_fd = (max_fdstate) { + case DONE: + /* do nothing */ + break; + + case RECV_REQUEST: + MAX_FD_SET(conn->socket, &recv_set); + bother_with_timeout = 1; + break; + + case SEND_HEADER: + case SEND_REPLY: + MAX_FD_SET(conn->socket, &send_set); + bother_with_timeout = 1; + break; + } + } +#undef MAX_FD_SET + +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) + __msan_unpoison(&recv_set, sizeof(recv_set)); + __msan_unpoison(&send_set, sizeof(send_set)); +# endif +#endif + + /* -select- */ + if (debug) { + printf("select() with max_fd %d timeout %d\n", + max_fd, bother_with_timeout ? (int)timeout.tv_sec : 0); + gettimeofday(&t0, NULL); + } + select_ret = select(max_fd + 1, &recv_set, &send_set, NULL, + (bother_with_timeout) ? &timeout : NULL); + if (select_ret == 0) { + if (!bother_with_timeout) + errx(1, "select() timed out"); + } + if (select_ret == -1) { + if (errno == EINTR) + return; /* interrupted by signal */ + else + err(1, "select() failed"); + } + if (debug) { + long long sec, usec; + gettimeofday(&t1, NULL); + sec = t1.tv_sec - t0.tv_sec; + usec = t1.tv_usec - t0.tv_usec; + if (usec < 0) { + usec += 1000000; + sec--; + } + printf("select() returned %d after %lld.%06lld secs\n", + select_ret, sec, usec); + } + + /* update time */ + now = time(NULL); + + /* poll connections that select() says need attention */ + if (FD_ISSET(sockin, &recv_set)) + accept_connection(); + + LIST_FOREACH_SAFE(conn, &connlist, entries, next) { + poll_check_timeout(conn); + switch (conn->state) { + case RECV_REQUEST: + if (FD_ISSET(conn->socket, &recv_set)) poll_recv_request(conn); + break; + + case SEND_HEADER: + if (FD_ISSET(conn->socket, &send_set)) poll_send_header(conn); + break; + + case SEND_REPLY: + if (FD_ISSET(conn->socket, &send_set)) poll_send_reply(conn); + break; + + case DONE: + /* (handled later; ignore for now as it's a valid state) */ + break; + } + + /* Handling SEND_REPLY could have set the state to done. */ + if (conn->state == DONE) { + /* clean out finished connection */ + if (conn->conn_close) { + LIST_REMOVE(conn, entries); + free_connection(conn); + free(conn); + } else { + recycle_connection(conn); + /* and go right back to recv_request without going through + * select() again. + */ + poll_recv_request(conn); + } + } + } +} + +/* Daemonize helpers. */ +#define PATH_DEVNULL "/dev/null" +static int lifeline[2] = { -1, -1 }; +static int fd_null = -1; + +static void daemonize_start(void) { + pid_t f; + + if (pipe(lifeline) == -1) + err(1, "pipe(lifeline)"); + + fd_null = open(PATH_DEVNULL, O_RDWR, 0); + if (fd_null == -1) + err(1, "open(" PATH_DEVNULL ")"); + + f = fork(); + if (f == -1) + err(1, "fork"); + else if (f != 0) { + /* parent: wait for child */ + char tmp[1]; + int status; + pid_t w; + + if (close(lifeline[1]) == -1) + warn("close lifeline in parent"); + if (read(lifeline[0], tmp, sizeof(tmp)) == -1) + warn("read lifeline in parent"); + w = waitpid(f, &status, WNOHANG); + if (w == -1) + err(1, "waitpid"); + else if (w == 0) + /* child is running happily */ + exit(EXIT_SUCCESS); + else + /* child init failed, pass on its exit status */ + exit(WEXITSTATUS(status)); + } + /* else we are the child: continue initializing */ +} + +static void daemonize_finish(void) { + if (fd_null == -1) + return; /* didn't daemonize_start() so we're not daemonizing */ + + if (setsid() == -1) + err(1, "setsid"); + if (close(lifeline[0]) == -1) + warn("close read end of lifeline in child"); + if (close(lifeline[1]) == -1) + warn("couldn't cut the lifeline"); + + /* close all our std fds */ + if (dup2(fd_null, STDIN_FILENO) == -1) + warn("dup2(stdin)"); + if (dup2(fd_null, STDOUT_FILENO) == -1) + warn("dup2(stdout)"); + if (dup2(fd_null, STDERR_FILENO) == -1) + warn("dup2(stderr)"); + if (fd_null > 2) + close(fd_null); +} + +/* [->] pidfile helpers, based on FreeBSD src/lib/libutil/pidfile.c,v 1.3 + * Original was copyright (c) 2005 Pawel Jakub Dawidek + */ +static int pidfile_fd = -1; +#define PIDFILE_MODE 0600 + +static void pidfile_remove(void) { + if (unlink(pidfile_name) == -1) + err(1, "unlink(pidfile) failed"); + /* if (flock(pidfile_fd, LOCK_UN) == -1) + err(1, "unlock(pidfile) failed"); */ + xclose(pidfile_fd); + pidfile_fd = -1; +} + +static int pidfile_read(void) { + char buf[16]; + int fd, i; + long long pid; + + fd = open(pidfile_name, O_RDONLY); + if (fd == -1) + err(1, " after create failed"); + + i = (int)read(fd, buf, sizeof(buf) - 1); + if (i == -1) + err(1, "read from pidfile failed"); + xclose(fd); + buf[i] = '\0'; + + if (!str_to_num(buf, &pid)) { + err(1, "invalid pidfile contents: \"%s\"", buf); + } + return (int)pid; +} + +static void pidfile_create(void) { + int error, fd; + char pidstr[16]; + + /* Open the PID file and obtain exclusive lock. */ + fd = open(pidfile_name, + O_WRONLY | O_CREAT | O_EXLOCK | O_TRUNC | O_NONBLOCK, PIDFILE_MODE); + if (fd == -1) { + if ((errno == EWOULDBLOCK) || (errno == EEXIST)) + errx(1, "daemon already running with PID %d", pidfile_read()); + else + err(1, "can't create pidfile %s", pidfile_name); + } + pidfile_fd = fd; + + if (ftruncate(fd, 0) == -1) { + error = errno; + pidfile_remove(); + errno = error; + err(1, "ftruncate() failed"); + } + + snprintf(pidstr, sizeof(pidstr), "%d", (int)getpid()); + if (pwrite(fd, pidstr, strlen(pidstr), 0) != (ssize_t)strlen(pidstr)) { + error = errno; + pidfile_remove(); + errno = error; + err(1, "pwrite() failed"); + } +} +/* [<-] end of pidfile helpers. */ + +/* Close all sockets and FILEs and exit. */ +static void stop_running(int sig unused) { + running = 0; +} + +/* Execution starts here. */ +int main(int argc, char **argv) { + printf("%s, %s.\n", pkgname, copyright); + parse_default_extension_map(); + parse_commandline(argc, argv); + /* parse_commandline() might override parts of the extension map by + * parsing a user-specified file. + */ + sort_mime_map(); + xasprintf(&keep_alive_field, "Keep-Alive: timeout=%d\r\n", timeout_secs); + if (want_server_id) + xasprintf(&server_hdr, "Server: %s\r\n", pkgname); + else + server_hdr = xstrdup(""); + init_sockin(); + + /* open logfile */ + if (logfile_name == NULL) + logfile = stdout; + else { + logfile = fopen(logfile_name, "ab"); + if (logfile == NULL) + err(1, "opening logfile: fopen(\"%s\")", logfile_name); + } + + if (want_daemon) + daemonize_start(); + + /* signals */ + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + err(1, "signal(ignore SIGPIPE)"); + if (signal(SIGINT, stop_running) == SIG_ERR) + err(1, "signal(SIGINT)"); + if (signal(SIGTERM, stop_running) == SIG_ERR) + err(1, "signal(SIGTERM)"); + + /* security */ + if (want_chroot) { + tzset(); /* read /etc/localtime before we chroot */ + if (chdir(wwwroot) == -1) + err(1, "chdir(%s)", wwwroot); + if (chroot(wwwroot) == -1) + err(1, "chroot(%s)", wwwroot); + printf("chrooted to `%s'\n", wwwroot); + wwwroot[0] = '\0'; /* empty string */ + } + if (drop_gid != INVALID_GID) { + gid_t list[1]; + list[0] = drop_gid; + if (setgroups(1, list) == -1) + err(1, "setgroups([%d])", (int)drop_gid); + if (setgid(drop_gid) == -1) + err(1, "setgid(%d)", (int)drop_gid); + printf("set gid to %d\n", (int)drop_gid); + } + if (drop_uid != INVALID_UID) { + if (setuid(drop_uid) == -1) + err(1, "setuid(%d)", (int)drop_uid); + printf("set uid to %d\n", (int)drop_uid); + } + + /* create pidfile */ + if (pidfile_name) pidfile_create(); + + if (want_daemon) daemonize_finish(); + + /* main loop */ + while (running) httpd_poll(); + + /* clean exit */ + xclose(sockin); + if (logfile != NULL) fclose(logfile); + if (pidfile_name) pidfile_remove(); + + /* close and free connections */ + { + struct connection *conn, *next; + + LIST_FOREACH_SAFE(conn, &connlist, entries, next) { + LIST_REMOVE(conn, entries); + free_connection(conn); + free(conn); + } + } + + /* free the mallocs */ + { + size_t i; + for (i=0; i - -Copyright 2004 Ted Unangst -Copyright 2004 Todd C. Miller -Copyright 2008 Otto Moerbeek -Copyright 2017-2018 Hiltjo Posthuma -Copyright 2017-2021 Quentin Rameau -Copyright 2018 Josuah Demangeon -Copyright 2018 Dominik Schmidt -Copyright 2018 Aaron Burrow -Copyright 2020 Nihal Jere -Copyright 2020 Rainer Holzner -Copyright 2020 Jeremy Bobbin - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/.local/src/quark/Makefile b/.local/src/quark/Makefile deleted file mode 100644 index 49226214..00000000 --- a/.local/src/quark/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -# See LICENSE file for copyright and license details -# quark - simple web server -.POSIX: - -include config.mk - -COMPONENTS = connection data http queue server sock util - -all: quark - -connection.o: connection.c config.h connection.h data.h http.h server.h sock.h util.h config.mk -data.o: data.c config.h data.h http.h server.h util.h config.mk -http.o: http.c config.h http.h server.h util.h config.mk -main.o: main.c arg.h config.h server.h sock.h util.h config.mk -server.o: server.c config.h connection.h http.h queue.h server.h util.h config.mk -sock.o: sock.c config.h sock.h util.h config.mk -util.o: util.c config.h util.h config.mk - -quark: config.h $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk - $(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS) - -config.h: - cp config.def.h $@ - -clean: - rm -f quark main.o $(COMPONENTS:=.o) - -dist: - rm -rf "quark-$(VERSION)" - mkdir -p "quark-$(VERSION)" - cp -R LICENSE Makefile arg.h config.def.h config.mk quark.1 \ - $(COMPONENTS:=.c) $(COMPONENTS:=.h) main.c "quark-$(VERSION)" - tar -cf - "quark-$(VERSION)" | gzip -c > "quark-$(VERSION).tar.gz" - rm -rf "quark-$(VERSION)" - -install: all - mkdir -p "$(DESTDIR)$(PREFIX)/bin" - cp -f quark "$(DESTDIR)$(PREFIX)/bin" - chmod 755 "$(DESTDIR)$(PREFIX)/bin/quark" - mkdir -p "$(DESTDIR)$(MANPREFIX)/man1" - cp quark.1 "$(DESTDIR)$(MANPREFIX)/man1/quark.1" - chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/quark.1" - -uninstall: - rm -f "$(DESTDIR)$(PREFIX)/bin/quark" - rm -f "$(DESTDIR)$(MANPREFIX)/man1/quark.1" diff --git a/.local/src/quark/arg.h b/.local/src/quark/arg.h deleted file mode 100644 index 35cc7cce..00000000 --- a/.local/src/quark/arg.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * ISC-License - * - * Copyright 2004-2017 Christoph Lohmann <20h@r-36.net> - * Copyright 2017-2018 Laslo Hunhold - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -#ifndef ARG_H -#define ARG_H - -extern char *argv0; - -/* int main(int argc, char *argv[]) */ -#define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \ - *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \ - int i_, argused_; \ - if ((*argv)[1] == '-' && !(*argv)[2]) { \ - argc--, argv++; \ - break; \ - } \ - for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \ - switch((*argv)[i_]) -#define ARGEND if (argused_) { \ - if ((*argv)[i_ + 1]) { \ - break; \ - } else { \ - argc--, argv++; \ - break; \ - } \ - } \ - } \ - } -#define ARGC() ((*argv)[i_]) -#define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \ - (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x)) -#define EARGF(x) ARGF_(((x), exit(1), (char *)0)) -#define ARGF() ARGF_((char *)0) - -#endif diff --git a/.local/src/quark/config.h b/.local/src/quark/config.h deleted file mode 100644 index 406fce5d..00000000 --- a/.local/src/quark/config.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#define BUFFER_SIZE 4096 -#define FIELD_MAX 200 - -/* mime-types */ -static const struct { - char *ext; - char *type; -} mimes[] = { - { "xml", "application/xml; charset=utf-8" }, - { "xhtml", "application/xhtml+xml; charset=utf-8" }, - { "html", "text/html; charset=utf-8" }, - { "js", "text/javascript; charset=utf-8" }, - { "htm", "text/html; charset=utf-8" }, - { "css", "text/css; charset=utf-8" }, - { "txt", "text/plain; charset=utf-8" }, - { "md", "text/plain; charset=utf-8" }, - { "c", "text/plain; charset=utf-8" }, - { "h", "text/plain; charset=utf-8" }, - { "gz", "application/x-gtar" }, - { "tar", "application/tar" }, - { "pdf", "application/x-pdf" }, - { "png", "image/png" }, - { "gif", "image/gif" }, - { "jpeg", "image/jpg" }, - { "jpg", "image/jpg" }, - { "iso", "application/x-iso9660-image" }, - { "webp", "image/webp" }, - { "svg", "image/svg+xml; charset=utf-8" }, - { "flac", "audio/flac" }, - { "mp3", "audio/mpeg" }, - { "ogg", "audio/ogg" }, - { "mp4", "video/mp4" }, - { "ogv", "video/ogg" }, - { "webm", "video/webm" }, -}; - -#endif /* CONFIG_H */ diff --git a/.local/src/quark/config.mk b/.local/src/quark/config.mk deleted file mode 100644 index cedb3e7c..00000000 --- a/.local/src/quark/config.mk +++ /dev/null @@ -1,16 +0,0 @@ -# quark version -VERSION = 0 - -# Customize below to fit your system - -# paths -PREFIX = /usr/local -MANPREFIX = $(PREFIX)/share/man - -# flags -CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -CFLAGS = -std=c99 -pedantic -Wall -Wextra -Os -LDFLAGS = -lpthread -s - -# compiler and linker -CC = cc diff --git a/.local/src/quark/connection.c b/.local/src/quark/connection.c deleted file mode 100644 index 8aca2ab5..00000000 --- a/.local/src/quark/connection.c +++ /dev/null @@ -1,314 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "connection.h" -#include "data.h" -#include "http.h" -#include "server.h" -#include "sock.h" -#include "util.h" - -struct worker_data { - int insock; - size_t nslots; - const struct server *srv; -}; - -void -connection_log(const struct connection *c) -{ - char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; - char tstmp[21]; - - /* create timestamp */ - if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ", - gmtime(&(time_t){time(NULL)}))) { - warn("strftime: Exceeded buffer capacity"); - /* continue anyway (we accept the truncation) */ - } - - /* generate address-string */ - if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) { - warn("sock_get_inaddr_str: Couldn't generate adress-string"); - inaddr_str[0] = '\0'; - } - - printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n", - tstmp, - inaddr_str, - (c->res.status == 0) ? "dropped" : "", - (c->res.status == 0) ? 0 : 3, - c->res.status, - c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-", - c->req.path[0] ? c->req.path : "-", - c->req.query[0] ? "?" : "", - c->req.query, - c->req.fragment[0] ? "#" : "", - c->req.fragment); -} - -void -connection_reset(struct connection *c) -{ - if (c != NULL) { - shutdown(c->fd, SHUT_RDWR); - close(c->fd); - memset(c, 0, sizeof(*c)); - } -} - -void -connection_serve(struct connection *c, const struct server *srv) -{ - enum status s; - int done; - - switch (c->state) { - case C_VACANT: - /* - * we were passed a "fresh" connection which should now - * try to receive the header, reset buf beforehand - */ - memset(&c->buf, 0, sizeof(c->buf)); - - c->state = C_RECV_HEADER; - /* fallthrough */ - case C_RECV_HEADER: - /* receive header */ - done = 0; - if ((s = http_recv_header(c->fd, &c->buf, &done))) { - http_prepare_error_response(&c->req, &c->res, s); - goto response; - } - if (!done) { - /* not done yet */ - return; - } - - /* parse header */ - if ((s = http_parse_header(c->buf.data, &c->req))) { - http_prepare_error_response(&c->req, &c->res, s); - goto response; - } - - /* prepare response struct */ - http_prepare_response(&c->req, &c->res, srv); -response: - /* generate response header */ - if ((s = http_prepare_header_buf(&c->res, &c->buf))) { - http_prepare_error_response(&c->req, &c->res, s); - if ((s = http_prepare_header_buf(&c->res, &c->buf))) { - /* couldn't generate the header, we failed for good */ - c->res.status = s; - goto err; - } - } - - c->state = C_SEND_HEADER; - /* fallthrough */ - case C_SEND_HEADER: - if ((s = http_send_buf(c->fd, &c->buf))) { - c->res.status = s; - goto err; - } - if (c->buf.len > 0) { - /* not done yet */ - return; - } - - c->state = C_SEND_BODY; - /* fallthrough */ - case C_SEND_BODY: - if (c->req.method == M_GET) { - if (c->buf.len == 0) { - /* fill buffer with body data */ - if ((s = data_fct[c->res.type](&c->res, &c->buf, - &c->progress))) { - /* too late to do any real error handling */ - c->res.status = s; - goto err; - } - - /* if the buffer remains empty, we are done */ - if (c->buf.len == 0) { - break; - } - } else { - /* send buffer */ - if ((s = http_send_buf(c->fd, &c->buf))) { - /* too late to do any real error handling */ - c->res.status = s; - goto err; - } - } - return; - } - break; - default: - warn("serve: invalid connection state"); - return; - } -err: - connection_log(c); - connection_reset(c); -} - -static struct connection * -connection_get_drop_candidate(struct connection *connection, size_t nslots) -{ - struct connection *c, *minc; - size_t i, j, maxcnt, cnt; - - /* - * determine the most-unimportant connection 'minc' of the in-address - * with most connections; this algorithm has a complexity of O(n²) - * in time but is O(1) in space; there are algorithms with O(n) in - * time and space, but this would require memory allocation, - * which we avoid. Given the simplicity of the inner loop and - * relatively small number of slots per thread, this is fine. - */ - for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) { - /* - * we determine how many connections have the same - * in-address as connection[i], but also minimize over - * that set with other criteria, yielding a general - * minimizer c. We first set it to connection[i] and - * update it, if a better candidate shows up, in the inner - * loop - */ - c = &connection[i]; - - for (j = 0, cnt = 0; j < nslots; j++) { - if (!sock_same_addr(&connection[i].ia, - &connection[j].ia)) { - continue; - } - cnt++; - - /* minimize over state */ - if (connection[j].state < c->state) { - c = &connection[j]; - } else if (connection[j].state == c->state) { - /* minimize over progress */ - if (c->state == C_SEND_BODY && - connection[i].res.type != c->res.type) { - /* - * mixed response types; progress - * is not comparable - * - * the res-type-enum is ordered as - * DIRLISTING, ERROR, FILE, i.e. - * in rising priority, because a - * file transfer is most important, - * followed by error-messages. - * Dirlistings as an "interactive" - * feature (that take up lots of - * resources) have the lowest - * priority - */ - if (connection[i].res.type < - c->res.type) { - c = &connection[j]; - } - } else if (connection[j].progress < - c->progress) { - /* - * for C_SEND_BODY with same response - * type, C_RECV_HEADER and C_SEND_BODY - * it is sufficient to compare the - * raw progress - */ - c = &connection[j]; - } - } - } - - if (cnt > maxcnt) { - /* this run yielded an even greedier in-address */ - minc = c; - maxcnt = cnt; - } - } - - return minc; -} - -struct connection * -connection_accept(int insock, struct connection *connection, size_t nslots) -{ - struct connection *c = NULL; - size_t i; - - /* find vacant connection (i.e. one with no fd assigned to it) */ - for (i = 0; i < nslots; i++) { - if (connection[i].fd == 0) { - c = &connection[i]; - break; - } - } - if (i == nslots) { - /* - * all our connection-slots are occupied and the only - * way out is to drop another connection, because not - * accepting this connection just kicks this can further - * down the road (to the next queue_wait()) without - * solving anything. - * - * This may sound bad, but this case can only be hit - * either when there's a (D)DoS-attack or a massive - * influx of requests. The latter is impossible to solve - * at this moment without expanding resources, but the - * former has certain characteristics allowing us to - * handle this gracefully. - * - * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow - * Read or just plain flooding) we can not see who is - * waiting to be accept()ed. - * However, an attacker usually already has many - * connections open (while well-behaved clients could - * do everything with just one connection using - * keep-alive). Inferring a likely attacker-connection - * is an educated guess based on which in-address is - * occupying the most connection slots. Among those, - * connections in early stages (receiving or sending - * headers) are preferred over connections in late - * stages (sending body). - * - * This quantitative approach effectively drops malicious - * connections while preserving even long-running - * benevolent connections like downloads. - */ - c = connection_get_drop_candidate(connection, nslots); - c->res.status = 0; - connection_log(c); - connection_reset(c); - } - - /* accept connection */ - if ((c->fd = accept(insock, (struct sockaddr *)&c->ia, - &(socklen_t){sizeof(c->ia)})) < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - /* - * this should not happen, as we received the - * event that there are pending connections here - */ - warn("accept:"); - } - return NULL; - } - - /* set socket to non-blocking mode */ - if (sock_set_nonblocking(c->fd)) { - /* we can't allow blocking sockets */ - return NULL; - } - - return c; -} diff --git a/.local/src/quark/connection.h b/.local/src/quark/connection.h deleted file mode 100644 index 5b725c08..00000000 --- a/.local/src/quark/connection.h +++ /dev/null @@ -1,32 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef CONNECTION_H -#define CONNECTION_H - -#include "http.h" -#include "server.h" -#include "util.h" - -enum connection_state { - C_VACANT, - C_RECV_HEADER, - C_SEND_HEADER, - C_SEND_BODY, - NUM_CONN_STATES, -}; - -struct connection { - enum connection_state state; - int fd; - struct sockaddr_storage ia; - struct request req; - struct response res; - struct buffer buf; - size_t progress; -}; - -struct connection *connection_accept(int, struct connection *, size_t); -void connection_log(const struct connection *); -void connection_reset(struct connection *); -void connection_serve(struct connection *, const struct server *); - -#endif /* CONNECTION_H */ diff --git a/.local/src/quark/data.c b/.local/src/quark/data.c deleted file mode 100644 index 4f20186f..00000000 --- a/.local/src/quark/data.c +++ /dev/null @@ -1,231 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include - -#include "data.h" -#include "http.h" -#include "util.h" - -enum status (* const data_fct[])(const struct response *, - struct buffer *, size_t *) = { - [RESTYPE_DIRLISTING] = data_prepare_dirlisting_buf, - [RESTYPE_ERROR] = data_prepare_error_buf, - [RESTYPE_FILE] = data_prepare_file_buf, -}; - -static int -compareent(const struct dirent **d1, const struct dirent **d2) -{ - int v; - - v = ((*d2)->d_type == DT_DIR ? 1 : -1) - - ((*d1)->d_type == DT_DIR ? 1 : -1); - if (v) { - return v; - } - - return strcmp((*d1)->d_name, (*d2)->d_name); -} - -static char * -suffix(int t) -{ - switch (t) { - case DT_FIFO: return "|"; - case DT_DIR: return "/"; - case DT_LNK: return "@"; - case DT_SOCK: return "="; - } - - return ""; -} - -static void -html_escape(const char *src, char *dst, size_t dst_siz) -{ - const struct { - char c; - char *s; - } escape[] = { - { '&', "&" }, - { '<', "<" }, - { '>', ">" }, - { '"', """ }, - { '\'', "'" }, - }; - size_t i, j, k, esclen; - - for (i = 0, j = 0; src[i] != '\0'; i++) { - for (k = 0; k < LEN(escape); k++) { - if (src[i] == escape[k].c) { - break; - } - } - if (k == LEN(escape)) { - /* no escape char at src[i] */ - if (j == dst_siz - 1) { - /* silent truncation */ - break; - } else { - dst[j++] = src[i]; - } - } else { - /* escape char at src[i] */ - esclen = strlen(escape[k].s); - - if (j >= dst_siz - esclen) { - /* silent truncation */ - break; - } else { - memcpy(&dst[j], escape[k].s, esclen); - j += esclen; - } - } - } - dst[j] = '\0'; -} - -enum status -data_prepare_dirlisting_buf(const struct response *res, - struct buffer *buf, size_t *progress) -{ - enum status s = 0; - struct dirent **e; - size_t i; - int dirlen; - char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */ - - /* reset buffer */ - memset(buf, 0, sizeof(*buf)); - - /* read directory */ - if ((dirlen = scandir(res->internal_path, &e, NULL, compareent)) < 0) { - return S_FORBIDDEN; - } - - if (*progress == 0) { - /* write listing header (sizeof(esc) >= PATH_MAX) */ - html_escape(res->path, esc, MIN(PATH_MAX, sizeof(esc))); - if (buffer_appendf(buf, - "\n\n\t" - "Index of %s\n" - "\t\n\t\t..", - esc) < 0) { - s = S_REQUEST_TIMEOUT; - goto cleanup; - } - } - - /* listing entries */ - for (i = *progress; i < (size_t)dirlen; i++) { - /* skip hidden files, "." and ".." */ - if (e[i]->d_name[0] == '.') { - continue; - } - - /* entry line */ - html_escape(e[i]->d_name, esc, sizeof(esc)); - if (buffer_appendf(buf, - "
\n\t\t%s%s", - esc, - (e[i]->d_type == DT_DIR) ? "/" : "", - esc, - suffix(e[i]->d_type))) { - /* buffer full */ - break; - } - } - *progress = i; - - if (*progress == (size_t)dirlen) { - /* listing footer */ - if (buffer_appendf(buf, "\n\t\n\n") < 0) { - s = S_REQUEST_TIMEOUT; - goto cleanup; - } - (*progress)++; - } - -cleanup: - while (dirlen--) { - free(e[dirlen]); - } - free(e); - - return s; -} - -enum status -data_prepare_error_buf(const struct response *res, struct buffer *buf, - size_t *progress) -{ - /* reset buffer */ - memset(buf, 0, sizeof(*buf)); - - if (*progress == 0) { - /* write error body */ - if (buffer_appendf(buf, - "\n\n\t\n" - "\t\t%d %s\n\t\n" - "\t\n\t\t

%d %s

\n" - "\t\n\n", - res->status, status_str[res->status], - res->status, status_str[res->status])) { - return S_INTERNAL_SERVER_ERROR; - } - (*progress)++; - } - - return 0; -} - -enum status -data_prepare_file_buf(const struct response *res, struct buffer *buf, - size_t *progress) -{ - FILE *fp; - enum status s = 0; - ssize_t r; - size_t remaining; - - /* reset buffer */ - memset(buf, 0, sizeof(*buf)); - - /* open file */ - if (!(fp = fopen(res->internal_path, "r"))) { - s = S_FORBIDDEN; - goto cleanup; - } - - /* seek to lower bound + progress */ - if (fseek(fp, res->file.lower + *progress, SEEK_SET)) { - s = S_INTERNAL_SERVER_ERROR; - goto cleanup; - } - - /* read data into buf */ - remaining = res->file.upper - res->file.lower + 1 - *progress; - while ((r = fread(buf->data + buf->len, 1, - MIN(sizeof(buf->data) - buf->len, - remaining), fp))) { - if (r < 0) { - s = S_INTERNAL_SERVER_ERROR; - goto cleanup; - } - buf->len += r; - *progress += r; - remaining -= r; - } - -cleanup: - if (fp) { - fclose(fp); - } - - return s; -} diff --git a/.local/src/quark/data.h b/.local/src/quark/data.h deleted file mode 100644 index 77d18bac..00000000 --- a/.local/src/quark/data.h +++ /dev/null @@ -1,18 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef DATA_H -#define DATA_H - -#include "http.h" -#include "util.h" - -extern enum status (* const data_fct[])(const struct response *, - struct buffer *, size_t *); - -enum status data_prepare_dirlisting_buf(const struct response *, - struct buffer *, size_t *); -enum status data_prepare_error_buf(const struct response *, - struct buffer *, size_t *); -enum status data_prepare_file_buf(const struct response *, - struct buffer *, size_t *); - -#endif /* DATA_H */ diff --git a/.local/src/quark/http.c b/.local/src/quark/http.c deleted file mode 100644 index 5b9dade1..00000000 --- a/.local/src/quark/http.c +++ /dev/null @@ -1,1044 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "http.h" -#include "util.h" - -const char *req_field_str[] = { - [REQ_HOST] = "Host", - [REQ_RANGE] = "Range", - [REQ_IF_MODIFIED_SINCE] = "If-Modified-Since", -}; - -const char *req_method_str[] = { - [M_GET] = "GET", - [M_HEAD] = "HEAD", -}; - -const char *status_str[] = { - [S_OK] = "OK", - [S_PARTIAL_CONTENT] = "Partial Content", - [S_MOVED_PERMANENTLY] = "Moved Permanently", - [S_NOT_MODIFIED] = "Not Modified", - [S_BAD_REQUEST] = "Bad Request", - [S_FORBIDDEN] = "Forbidden", - [S_NOT_FOUND] = "Not Found", - [S_METHOD_NOT_ALLOWED] = "Method Not Allowed", - [S_REQUEST_TIMEOUT] = "Request Time-out", - [S_RANGE_NOT_SATISFIABLE] = "Range Not Satisfiable", - [S_REQUEST_TOO_LARGE] = "Request Header Fields Too Large", - [S_INTERNAL_SERVER_ERROR] = "Internal Server Error", - [S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported", -}; - -const char *res_field_str[] = { - [RES_ACCEPT_RANGES] = "Accept-Ranges", - [RES_ALLOW] = "Allow", - [RES_LOCATION] = "Location", - [RES_LAST_MODIFIED] = "Last-Modified", - [RES_CONTENT_LENGTH] = "Content-Length", - [RES_CONTENT_RANGE] = "Content-Range", - [RES_CONTENT_TYPE] = "Content-Type", -}; - -enum status -http_prepare_header_buf(const struct response *res, struct buffer *buf) -{ - char tstmp[FIELD_MAX]; - size_t i; - - /* reset buffer */ - memset(buf, 0, sizeof(*buf)); - - /* generate timestamp */ - if (timestamp(tstmp, sizeof(tstmp), time(NULL))) { - goto err; - } - - /* write data */ - if (buffer_appendf(buf, - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n" - "Connection: close\r\n", - res->status, status_str[res->status], tstmp)) { - goto err; - } - - for (i = 0; i < NUM_RES_FIELDS; i++) { - if (res->field[i][0] != '\0' && - buffer_appendf(buf, "%s: %s\r\n", res_field_str[i], - res->field[i])) { - goto err; - } - } - - if (buffer_appendf(buf, "\r\n")) { - goto err; - } - - return 0; -err: - memset(buf, 0, sizeof(*buf)); - return S_INTERNAL_SERVER_ERROR; -} - -enum status -http_send_buf(int fd, struct buffer *buf) -{ - ssize_t r; - - if (buf == NULL) { - return S_INTERNAL_SERVER_ERROR; - } - - while (buf->len > 0) { - if ((r = write(fd, buf->data, buf->len)) <= 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - /* - * socket is blocking, return normally. - * given the buffer still contains data, - * this indicates to the caller that we - * have been interrupted. - */ - return 0; - } else { - return S_REQUEST_TIMEOUT; - } - } - memmove(buf->data, buf->data + r, buf->len - r); - buf->len -= r; - } - - return 0; -} - -static void -decode(const char src[PATH_MAX], char dest[PATH_MAX]) -{ - size_t i; - uint8_t n; - const char *s; - - for (s = src, i = 0; *s; s++, i++) { - if (*s == '%' && (sscanf(s + 1, "%2hhx", &n) == 1)) { - dest[i] = n; - s += 2; - } else { - dest[i] = *s; - } - } - dest[i] = '\0'; -} - -enum status -http_recv_header(int fd, struct buffer *buf, int *done) -{ - enum status s; - ssize_t r; - - while (1) { - if ((r = read(fd, buf->data + buf->len, - sizeof(buf->data) - buf->len)) < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - /* - * socket is drained, return normally, - * but set done to zero - */ - *done = 0; - return 0; - } else { - s = S_REQUEST_TIMEOUT; - goto err; - } - } else if (r == 0) { - /* - * unexpected EOF because the client probably - * hung up. This is technically a bad request, - * because it's incomplete - */ - s = S_BAD_REQUEST; - goto err; - } - buf->len += r; - - /* check if we are done (header terminated) */ - if (buf->len >= 4 && !memcmp(buf->data + buf->len - 4, - "\r\n\r\n", 4)) { - break; - } - - /* buffer is full or read over, but header is not terminated */ - if (r == 0 || buf->len == sizeof(buf->data)) { - s = S_REQUEST_TOO_LARGE; - goto err; - } - } - - /* header is complete, remove last \r\n and set done */ - buf->len -= 2; - *done = 1; - - return 0; -err: - memset(buf, 0, sizeof(*buf)); - return s; -} - -enum status -http_parse_header(const char *h, struct request *req) -{ - struct in6_addr addr; - size_t i, mlen; - const char *p, *q, *r, *s, *t; - char *m, *n; - - /* empty the request struct */ - memset(req, 0, sizeof(*req)); - - /* - * parse request line - */ - - /* METHOD */ - for (i = 0; i < NUM_REQ_METHODS; i++) { - mlen = strlen(req_method_str[i]); - if (!strncmp(req_method_str[i], h, mlen)) { - req->method = i; - break; - } - } - if (i == NUM_REQ_METHODS) { - return S_METHOD_NOT_ALLOWED; - } - - /* a single space must follow the method */ - if (h[mlen] != ' ') { - return S_BAD_REQUEST; - } - - /* basis for next step */ - p = h + mlen + 1; - - /* RESOURCE */ - - /* - * path?query#fragment - * ^ ^ ^ ^ - * | | | | - * p r s q - * - */ - if (!(q = strchr(p, ' '))) { - return S_BAD_REQUEST; - } - - /* search for first '?' */ - for (r = p; r < q; r++) { - if (!isprint(*r)) { - return S_BAD_REQUEST; - } - if (*r == '?') { - break; - } - } - if (r == q) { - /* not found */ - r = NULL; - } - - /* search for first '#' */ - for (s = p; s < q; s++) { - if (!isprint(*s)) { - return S_BAD_REQUEST; - } - if (*s == '#') { - break; - } - } - if (s == q) { - /* not found */ - s = NULL; - } - - if (r != NULL && s != NULL && s < r) { - /* - * '#' comes before '?' and thus the '?' is literal, - * because the query must come before the fragment - */ - r = NULL; - } - - /* write path using temporary endpointer t */ - if (r != NULL) { - /* resource contains a query, path ends at r */ - t = r; - } else if (s != NULL) { - /* resource contains only a fragment, path ends at s */ - t = s; - } else { - /* resource contains no queries, path ends at q */ - t = q; - } - if ((size_t)(t - p + 1) > LEN(req->path)) { - return S_REQUEST_TOO_LARGE; - } - memcpy(req->path, p, t - p); - req->path[t - p] = '\0'; - decode(req->path, req->path); - - /* write query if present */ - if (r != NULL) { - /* query ends either at s (if fragment present) or q */ - t = (s != NULL) ? s : q; - - if ((size_t)(t - (r + 1) + 1) > LEN(req->query)) { - return S_REQUEST_TOO_LARGE; - } - memcpy(req->query, r + 1, t - (r + 1)); - req->query[t - (r + 1)] = '\0'; - } - - /* write fragment if present */ - if (s != NULL) { - /* the fragment always starts at s + 1 and ends at q */ - if ((size_t)(q - (s + 1) + 1) > LEN(req->fragment)) { - return S_REQUEST_TOO_LARGE; - } - memcpy(req->fragment, s + 1, q - (s + 1)); - req->fragment[q - (s + 1)] = '\0'; - } - - /* basis for next step */ - p = q + 1; - - /* HTTP-VERSION */ - if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) { - return S_BAD_REQUEST; - } - p += sizeof("HTTP/") - 1; - if (strncmp(p, "1.0", sizeof("1.0") - 1) && - strncmp(p, "1.1", sizeof("1.1") - 1)) { - return S_VERSION_NOT_SUPPORTED; - } - p += sizeof("1.*") - 1; - - /* check terminator */ - if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) { - return S_BAD_REQUEST; - } - - /* basis for next step */ - p += sizeof("\r\n") - 1; - - /* - * parse request-fields - */ - - /* match field type */ - for (; *p != '\0';) { - for (i = 0; i < NUM_REQ_FIELDS; i++) { - if (!strncasecmp(p, req_field_str[i], - strlen(req_field_str[i]))) { - break; - } - } - if (i == NUM_REQ_FIELDS) { - /* unmatched field, skip this line */ - if (!(q = strstr(p, "\r\n"))) { - return S_BAD_REQUEST; - } - p = q + (sizeof("\r\n") - 1); - continue; - } - - p += strlen(req_field_str[i]); - - /* a single colon must follow the field name */ - if (*p != ':') { - return S_BAD_REQUEST; - } - - /* skip whitespace */ - for (++p; *p == ' ' || *p == '\t'; p++) - ; - - /* extract field content */ - if (!(q = strstr(p, "\r\n"))) { - return S_BAD_REQUEST; - } - if ((size_t)(q - p + 1) > LEN(req->field[i])) { - return S_REQUEST_TOO_LARGE; - } - memcpy(req->field[i], p, q - p); - req->field[i][q - p] = '\0'; - - /* go to next line */ - p = q + (sizeof("\r\n") - 1); - } - - /* - * clean up host - */ - - m = strrchr(req->field[REQ_HOST], ':'); - n = strrchr(req->field[REQ_HOST], ']'); - - /* strip port suffix but don't interfere with IPv6 bracket notation - * as per RFC 2732 */ - if (m && (!n || m > n)) { - /* port suffix must not be empty */ - if (*(m + 1) == '\0') { - return S_BAD_REQUEST; - } - *m = '\0'; - } - - /* strip the brackets from the IPv6 notation and validate the address */ - if (n) { - /* brackets must be on the outside */ - if (req->field[REQ_HOST][0] != '[' || *(n + 1) != '\0') { - return S_BAD_REQUEST; - } - - /* remove the right bracket */ - *n = '\0'; - m = req->field[REQ_HOST] + 1; - - /* validate the contained IPv6 address */ - if (inet_pton(AF_INET6, m, &addr) != 1) { - return S_BAD_REQUEST; - } - - /* copy it into the host field */ - memmove(req->field[REQ_HOST], m, n - m + 1); - } - - return 0; -} - -static void -encode(const char src[PATH_MAX], char dest[PATH_MAX]) -{ - size_t i; - const char *s; - - for (s = src, i = 0; *s && i < (PATH_MAX - 4); s++) { - if (iscntrl(*s) || (unsigned char)*s > 127) { - i += snprintf(dest + i, PATH_MAX - i, "%%%02X", - (unsigned char)*s); - } else { - dest[i] = *s; - i++; - } - } - dest[i] = '\0'; -} - -static enum status -path_normalize(char *uri, int *redirect) -{ - size_t len; - int last = 0; - char *p, *q; - - /* require and skip first slash */ - if (uri[0] != '/') { - return S_BAD_REQUEST; - } - p = uri + 1; - - /* get length of URI */ - len = strlen(p); - - for (; !last; ) { - /* bound uri component within (p,q) */ - if (!(q = strchr(p, '/'))) { - q = strchr(p, '\0'); - last = 1; - } - - if (*p == '\0') { - break; - } else if (p == q || (q - p == 1 && p[0] == '.')) { - /* "/" or "./" */ - goto squash; - } else if (q - p == 2 && p[0] == '.' && p[1] == '.') { - /* "../" */ - if (p != uri + 1) { - /* place p right after the previous / */ - for (p -= 2; p > uri && *p != '/'; p--); - p++; - } - goto squash; - } else { - /* move on */ - p = q + 1; - continue; - } -squash: - /* squash (p,q) into void */ - if (last) { - *p = '\0'; - len = p - uri; - } else { - memmove(p, q + 1, len - ((q + 1) - uri) + 2); - len -= (q + 1) - p; - } - if (redirect != NULL) { - *redirect = 1; - } - } - - return 0; -} - -static enum status -path_add_vhost_prefix(char uri[PATH_MAX], int *redirect, - const struct server *srv, const struct response *res) -{ - if (srv->vhost && res->vhost && res->vhost->prefix) { - if (prepend(uri, PATH_MAX, res->vhost->prefix)) { - return S_REQUEST_TOO_LARGE; - } - if (redirect != NULL) { - *redirect = 1; - } - } - - return 0; -} - -static enum status -path_apply_prefix_mapping(char uri[PATH_MAX], int *redirect, - const struct server *srv, const struct response *res) -{ - size_t i, len; - - for (i = 0; i < srv->map_len; i++) { - len = strlen(srv->map[i].from); - if (!strncmp(uri, srv->map[i].from, len)) { - /* - * if vhosts are enabled only apply mappings - * defined for the current canonical host - */ - if (srv->vhost && res->vhost && srv->map[i].chost && - strcmp(srv->map[i].chost, res->vhost->chost)) { - continue; - } - - /* swap out URI prefix */ - memmove(uri, uri + len, strlen(uri) + 1); - if (prepend(uri, PATH_MAX, srv->map[i].to)) { - return S_REQUEST_TOO_LARGE; - } - - if (redirect != NULL) { - *redirect = 1; - } - - /* break so we don't possibly hit an infinite loop */ - break; - } - } - - return 0; -} - -static enum status -path_ensure_dirslash(char uri[PATH_MAX], int *redirect) -{ - size_t len; - - /* append '/' to URI if not present */ - len = strlen(uri); - if (len + 1 + 1 > PATH_MAX) { - return S_REQUEST_TOO_LARGE; - } - if (len > 0 && uri[len - 1] != '/') { - uri[len] = '/'; - uri[len + 1] = '\0'; - if (redirect != NULL) { - *redirect = 1; - } - } - - return 0; -} - -static enum status -parse_range(const char *str, size_t size, size_t *lower, size_t *upper) -{ - char first[FIELD_MAX], last[FIELD_MAX]; - const char *p, *q, *r, *err; - - /* default to the complete range */ - *lower = 0; - *upper = size - 1; - - /* done if no range-string is given */ - if (str == NULL || *str == '\0') { - return 0; - } - - /* skip opening statement */ - if (strncmp(str, "bytes=", sizeof("bytes=") - 1)) { - return S_BAD_REQUEST; - } - p = str + (sizeof("bytes=") - 1); - - /* check string (should only contain numbers and a hyphen) */ - for (r = p, q = NULL; *r != '\0'; r++) { - if (*r < '0' || *r > '9') { - if (*r == '-') { - if (q != NULL) { - /* we have already seen a hyphen */ - return S_BAD_REQUEST; - } else { - /* place q after the hyphen */ - q = r + 1; - } - } else if (*r == ',' && r > p) { - /* - * we refuse to accept range-lists out - * of spite towards this horrible part - * of the spec - */ - return S_RANGE_NOT_SATISFIABLE; - } else { - return S_BAD_REQUEST; - } - } - } - if (q == NULL) { - /* the input string must contain a hyphen */ - return S_BAD_REQUEST; - } - r = q + strlen(q); - - /* - * byte-range=first-last\0 - * ^ ^ ^ - * | | | - * p q r - */ - - /* copy 'first' and 'last' to their respective arrays */ - if ((size_t)((q - 1) - p + 1) > sizeof(first) || - (size_t)(r - q + 1) > sizeof(last)) { - return S_REQUEST_TOO_LARGE; - } - memcpy(first, p, (q - 1) - p); - first[(q - 1) - p] = '\0'; - memcpy(last, q, r - q); - last[r - q] = '\0'; - - if (first[0] != '\0') { - /* - * range has format "first-last" or "first-", - * i.e. return bytes 'first' to 'last' (or the - * last byte if 'last' is not given), - * inclusively, and byte-numbering beginning at 0 - */ - *lower = strtonum(first, 0, MIN(SIZE_MAX, LLONG_MAX), - &err); - if (!err) { - if (last[0] != '\0') { - *upper = strtonum(last, 0, - MIN(SIZE_MAX, LLONG_MAX), - &err); - } else { - *upper = size - 1; - } - } - if (err) { - /* one of the strtonum()'s failed */ - return S_BAD_REQUEST; - } - - /* check ranges */ - if (*lower > *upper || *lower >= size) { - return S_RANGE_NOT_SATISFIABLE; - } - - /* adjust upper limit to be at most the last byte */ - *upper = MIN(*upper, size - 1); - } else { - /* last must not also be empty */ - if (last[0] == '\0') { - return S_BAD_REQUEST; - } - - /* - * Range has format "-num", i.e. return the 'num' - * last bytes - */ - - /* - * use upper as a temporary storage for 'num', - * as we know 'upper' is size - 1 - */ - *upper = strtonum(last, 0, MIN(SIZE_MAX, LLONG_MAX), &err); - if (err) { - return S_BAD_REQUEST; - } - - /* determine lower */ - if (*upper > size) { - /* more bytes requested than we have */ - *lower = 0; - } else { - *lower = size - *upper; - } - - /* set upper to the correct value */ - *upper = size - 1; - } - - return 0; -} - -void -http_prepare_response(const struct request *req, struct response *res, - const struct server *srv) -{ - enum status s, tmps; - struct in6_addr addr; - struct stat st; - struct tm tm = { 0 }; - size_t i; - int redirect, hasport, ipv6host; - static char tmppath[PATH_MAX]; - char *p, *mime; - - /* empty all response fields */ - memset(res, 0, sizeof(*res)); - - /* determine virtual host */ - if (srv->vhost) { - for (i = 0; i < srv->vhost_len; i++) { - if (!regexec(&(srv->vhost[i].re), - req->field[REQ_HOST], 0, NULL, 0)) { - /* we have a matching vhost */ - res->vhost = &(srv->vhost[i]); - break; - } - } - if (i == srv->vhost_len) { - s = S_NOT_FOUND; - goto err; - } - } - - /* copy request-path to response-path and clean it up */ - redirect = 0; - memcpy(res->path, req->path, MIN(sizeof(res->path), sizeof(req->path))); - if ((tmps = path_normalize(res->path, &redirect)) || - (tmps = path_add_vhost_prefix(res->path, &redirect, srv, res)) || - (tmps = path_apply_prefix_mapping(res->path, &redirect, srv, res)) || - (tmps = path_normalize(res->path, &redirect))) { - s = tmps; - goto err; - } - - /* redirect all non-canonical hosts to their canonical forms */ - if (srv->vhost && res->vhost && - strcmp(req->field[REQ_HOST], res->vhost->chost)) { - redirect = 1; - } - - /* reject all non-well-known hidden targets (see RFC 8615) */ - if (strstr(res->path, "/.") && strncmp(res->path, "/.well-known/", - sizeof("/.well-known/") - 1)) { - s = S_FORBIDDEN; - goto err; - } - - /* - * generate and stat internal path based on the cleaned up request - * path and the virtual host while ignoring query and fragment - * (valid according to RFC 3986) - */ - if (esnprintf(res->internal_path, sizeof(res->internal_path), "/%s/%s", - (srv->vhost && res->vhost) ? res->vhost->dir : "", - res->path)) { - s = S_REQUEST_TOO_LARGE; - goto err; - } - if ((tmps = path_normalize(res->internal_path, NULL))) { - s = tmps; - goto err; - } - if (stat(res->internal_path, &st) < 0) { - s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND; - goto err; - } - - /* - * if the path points at a directory, make sure both the path - * and internal path have a trailing slash - */ - if (S_ISDIR(st.st_mode)) { - if ((tmps = path_ensure_dirslash(res->path, &redirect)) || - (tmps = path_ensure_dirslash(res->internal_path, NULL))) { - s = tmps; - goto err; - } - } - - /* redirect if the path-cleanup necessitated it earlier */ - if (redirect) { - res->status = S_MOVED_PERMANENTLY; - - /* encode path */ - encode(res->path, tmppath); - - /* determine target location */ - if (srv->vhost && res->vhost) { - /* absolute redirection URL */ - - /* do we need to add a port to the Location? */ - hasport = srv->port && strcmp(srv->port, "80"); - - /* RFC 2732 specifies to use brackets for IPv6-addresses - * in URLs, so we need to check if our host is one and - * honor that later when we fill the "Location"-field */ - if ((ipv6host = inet_pton(AF_INET6, res->vhost->chost, - &addr)) < 0) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - - /* - * write location to response struct (re-including - * the query and fragment, if present) - */ - if (esnprintf(res->field[RES_LOCATION], - sizeof(res->field[RES_LOCATION]), - "//%s%s%s%s%s%s%s%s%s%s", - ipv6host ? "[" : "", - res->vhost->chost, - ipv6host ? "]" : "", - hasport ? ":" : "", - hasport ? srv->port : "", - tmppath, - req->query[0] ? "?" : "", - req->query, - req->fragment[0] ? "#" : "", - req->fragment)) { - s = S_REQUEST_TOO_LARGE; - goto err; - } - } else { - /* - * write relative redirection URI to response struct - * (re-including the query and fragment, if present) - */ - if (esnprintf(res->field[RES_LOCATION], - sizeof(res->field[RES_LOCATION]), - "%s%s%s%s%s", - tmppath, - req->query[0] ? "?" : "", - req->query, - req->fragment[0] ? "#" : "", - req->fragment)) { - s = S_REQUEST_TOO_LARGE; - goto err; - } - } - - return; - } - - if (S_ISDIR(st.st_mode)) { - /* - * when we serve a directory, we first check if there - * exists a directory index. If not, we either make - * a directory listing (if enabled) or send an error - */ - - /* - * append docindex to internal_path temporarily - * (internal_path is guaranteed to end with '/') - */ - if (esnprintf(tmppath, sizeof(tmppath), "%s%s", - res->internal_path, srv->docindex)) { - s = S_REQUEST_TOO_LARGE; - goto err; - } - - /* stat the temporary path, which must be a regular file */ - if (stat(tmppath, &st) < 0 || !S_ISREG(st.st_mode)) { - if (srv->listdirs) { - /* serve directory listing */ - - /* check if directory is accessible */ - if (access(res->internal_path, R_OK) != 0) { - s = S_FORBIDDEN; - goto err; - } else { - res->status = S_OK; - } - res->type = RESTYPE_DIRLISTING; - - if (esnprintf(res->field[RES_CONTENT_TYPE], - sizeof(res->field[RES_CONTENT_TYPE]), - "%s", "text/html; charset=utf-8")) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - - return; - } else { - /* reject */ - s = (!S_ISREG(st.st_mode) || errno == EACCES) ? - S_FORBIDDEN : S_NOT_FOUND; - goto err; - } - } else { - /* the docindex exists; copy tmppath to internal path */ - if (esnprintf(res->internal_path, - sizeof(res->internal_path), "%s", - tmppath)) { - s = S_REQUEST_TOO_LARGE; - goto err; - } - } - } - - /* modified since */ - if (req->field[REQ_IF_MODIFIED_SINCE][0]) { - /* parse field */ - if (!strptime(req->field[REQ_IF_MODIFIED_SINCE], - "%a, %d %b %Y %T GMT", &tm)) { - s = S_BAD_REQUEST; - goto err; - } - - /* compare with last modification date of the file */ - if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) { - res->status = S_NOT_MODIFIED; - return; - } - } - - /* range */ - if ((s = parse_range(req->field[REQ_RANGE], st.st_size, - &(res->file.lower), &(res->file.upper)))) { - if (s == S_RANGE_NOT_SATISFIABLE) { - res->status = S_RANGE_NOT_SATISFIABLE; - - if (esnprintf(res->field[RES_CONTENT_RANGE], - sizeof(res->field[RES_CONTENT_RANGE]), - "bytes */%zu", st.st_size)) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - - return; - } else { - goto err; - } - } - - /* mime */ - mime = "application/octet-stream"; - if ((p = strrchr(res->internal_path, '.'))) { - for (i = 0; i < LEN(mimes); i++) { - if (!strcmp(mimes[i].ext, p + 1)) { - mime = mimes[i].type; - break; - } - } - } - - /* fill response struct */ - res->type = RESTYPE_FILE; - - /* check if file is readable */ - res->status = (access(res->internal_path, R_OK)) ? S_FORBIDDEN : - (req->field[REQ_RANGE][0] != '\0') ? - S_PARTIAL_CONTENT : S_OK; - - if (esnprintf(res->field[RES_ACCEPT_RANGES], - sizeof(res->field[RES_ACCEPT_RANGES]), - "%s", "bytes")) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - - if (esnprintf(res->field[RES_CONTENT_LENGTH], - sizeof(res->field[RES_CONTENT_LENGTH]), - "%zu", res->file.upper - res->file.lower + 1)) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - if (req->field[REQ_RANGE][0] != '\0') { - if (esnprintf(res->field[RES_CONTENT_RANGE], - sizeof(res->field[RES_CONTENT_RANGE]), - "bytes %zd-%zd/%zu", res->file.lower, - res->file.upper, st.st_size)) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - } - if (esnprintf(res->field[RES_CONTENT_TYPE], - sizeof(res->field[RES_CONTENT_TYPE]), - "%s", mime)) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - if (timestamp(res->field[RES_LAST_MODIFIED], - sizeof(res->field[RES_LAST_MODIFIED]), - st.st_mtim.tv_sec)) { - s = S_INTERNAL_SERVER_ERROR; - goto err; - } - - return; -err: - http_prepare_error_response(req, res, s); -} - -void -http_prepare_error_response(const struct request *req, - struct response *res, enum status s) -{ - /* used later */ - (void)req; - - /* empty all response fields */ - memset(res, 0, sizeof(*res)); - - res->type = RESTYPE_ERROR; - res->status = s; - - if (esnprintf(res->field[RES_CONTENT_TYPE], - sizeof(res->field[RES_CONTENT_TYPE]), - "text/html; charset=utf-8")) { - res->status = S_INTERNAL_SERVER_ERROR; - } - - if (res->status == S_METHOD_NOT_ALLOWED) { - if (esnprintf(res->field[RES_ALLOW], - sizeof(res->field[RES_ALLOW]), - "Allow: GET, HEAD")) { - res->status = S_INTERNAL_SERVER_ERROR; - } - } -} diff --git a/.local/src/quark/http.h b/.local/src/quark/http.h deleted file mode 100644 index d08a3b46..00000000 --- a/.local/src/quark/http.h +++ /dev/null @@ -1,97 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef HTTP_H -#define HTTP_H - -#include -#include - -#include "config.h" -#include "server.h" -#include "util.h" - -enum req_field { - REQ_HOST, - REQ_RANGE, - REQ_IF_MODIFIED_SINCE, - NUM_REQ_FIELDS, -}; - -extern const char *req_field_str[]; - -enum req_method { - M_GET, - M_HEAD, - NUM_REQ_METHODS, -}; - -extern const char *req_method_str[]; - -struct request { - enum req_method method; - char path[PATH_MAX]; - char query[FIELD_MAX]; - char fragment[FIELD_MAX]; - char field[NUM_REQ_FIELDS][FIELD_MAX]; -}; - -enum status { - S_OK = 200, - S_PARTIAL_CONTENT = 206, - S_MOVED_PERMANENTLY = 301, - S_NOT_MODIFIED = 304, - S_BAD_REQUEST = 400, - S_FORBIDDEN = 403, - S_NOT_FOUND = 404, - S_METHOD_NOT_ALLOWED = 405, - S_REQUEST_TIMEOUT = 408, - S_RANGE_NOT_SATISFIABLE = 416, - S_REQUEST_TOO_LARGE = 431, - S_INTERNAL_SERVER_ERROR = 500, - S_VERSION_NOT_SUPPORTED = 505, -}; - -extern const char *status_str[]; - -enum res_field { - RES_ACCEPT_RANGES, - RES_ALLOW, - RES_LOCATION, - RES_LAST_MODIFIED, - RES_CONTENT_LENGTH, - RES_CONTENT_RANGE, - RES_CONTENT_TYPE, - NUM_RES_FIELDS, -}; - -extern const char *res_field_str[]; - -enum res_type { - RESTYPE_DIRLISTING, - RESTYPE_ERROR, - RESTYPE_FILE, - NUM_RES_TYPES, -}; - -struct response { - enum res_type type; - enum status status; - char field[NUM_RES_FIELDS][FIELD_MAX]; - char path[PATH_MAX]; - char internal_path[PATH_MAX]; - struct vhost *vhost; - struct { - size_t lower; - size_t upper; - } file; -}; - -enum status http_prepare_header_buf(const struct response *, struct buffer *); -enum status http_send_buf(int, struct buffer *); -enum status http_recv_header(int, struct buffer *, int *); -enum status http_parse_header(const char *, struct request *); -void http_prepare_response(const struct request *, struct response *, - const struct server *); -void http_prepare_error_response(const struct request *, - struct response *, enum status); - -#endif /* HTTP_H */ diff --git a/.local/src/quark/main.c b/.local/src/quark/main.c deleted file mode 100644 index 1e885362..00000000 --- a/.local/src/quark/main.c +++ /dev/null @@ -1,364 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "arg.h" -#include "server.h" -#include "sock.h" -#include "util.h" - -static char *udsname; - -static void -cleanup(void) -{ - if (udsname) { - sock_rem_uds(udsname); - } -} - -static void -sigcleanup(int sig) -{ - cleanup(); - kill(0, sig); - _exit(1); -} - -static void -handlesignals(void(*hdl)(int)) -{ - struct sigaction sa = { - .sa_handler = hdl, - }; - - sigemptyset(&sa.sa_mask); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); -} - -static void -usage(void) -{ - const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] " - "[-i file] [-v vhost] ... [-m map] ..."; - - die("usage: %s -p port [-h host] %s\n" - " %s -U file [-p port] %s", argv0, - opts, argv0, opts); -} - -int -main(int argc, char *argv[]) -{ - struct group *grp = NULL; - struct passwd *pwd = NULL; - struct rlimit rlim; - struct server srv = { - .docindex = "index.html", - }; - size_t i; - int insock, status = 0; - const char *err; - char *tok[4]; - - /* defaults */ - size_t nthreads = 4; - size_t nslots = 64; - char *servedir = "."; - char *user = "nobody"; - char *group = "nogroup"; - - ARGBEGIN { - case 'd': - servedir = EARGF(usage()); - break; - case 'g': - group = EARGF(usage()); - break; - case 'h': - srv.host = EARGF(usage()); - break; - case 'i': - srv.docindex = EARGF(usage()); - if (strchr(srv.docindex, '/')) { - die("The document index must not contain '/'"); - } - break; - case 'l': - srv.listdirs = 1; - break; - case 'm': - if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) { - usage(); - } - if (!(srv.map = reallocarray(srv.map, ++srv.map_len, - sizeof(struct map)))) { - die("reallocarray:"); - } - srv.map[srv.map_len - 1].from = tok[0]; - srv.map[srv.map_len - 1].to = tok[1]; - srv.map[srv.map_len - 1].chost = tok[2]; - break; - case 's': - err = NULL; - nslots = strtonum(EARGF(usage()), 1, INT_MAX, &err); - if (err) { - die("strtonum '%s': %s", EARGF(usage()), err); - } - break; - case 't': - err = NULL; - nthreads = strtonum(EARGF(usage()), 1, INT_MAX, &err); - if (err) { - die("strtonum '%s': %s", EARGF(usage()), err); - } - break; - case 'p': - srv.port = EARGF(usage()); - break; - case 'U': - udsname = EARGF(usage()); - break; - case 'u': - user = EARGF(usage()); - break; - case 'v': - if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] || - !tok[2]) { - usage(); - } - if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len, - sizeof(*srv.vhost)))) { - die("reallocarray:"); - } - srv.vhost[srv.vhost_len - 1].chost = tok[0]; - srv.vhost[srv.vhost_len - 1].regex = tok[1]; - srv.vhost[srv.vhost_len - 1].dir = tok[2]; - srv.vhost[srv.vhost_len - 1].prefix = tok[3]; - break; - default: - usage(); - } ARGEND - - if (argc) { - usage(); - } - - /* can't have both host and UDS but must have one of port or UDS*/ - if ((srv.host && udsname) || !(srv.port || udsname)) { - usage(); - } - - if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) { - die("UNIX-domain socket '%s': %s", udsname, errno ? - strerror(errno) : "File exists"); - } - - /* compile and check the supplied vhost regexes */ - for (i = 0; i < srv.vhost_len; i++) { - if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex, - REG_EXTENDED | REG_ICASE | REG_NOSUB)) { - die("regcomp '%s': invalid regex", - srv.vhost[i].regex); - } - } - - /* validate user and group */ - errno = 0; - if (!user || !(pwd = getpwnam(user))) { - die("getpwnam '%s': %s", user ? user : "null", - errno ? strerror(errno) : "Entry not found"); - } - errno = 0; - if (!group || !(grp = getgrnam(group))) { - die("getgrnam '%s': %s", group ? group : "null", - errno ? strerror(errno) : "Entry not found"); - } - - /* open a new process group */ - setpgid(0, 0); - - handlesignals(sigcleanup); - - /* - * set the maximum number of open file descriptors as needed - * - 3 initial fd's - * - nthreads fd's for the listening socket - * - (nthreads * nslots) fd's for the connection-fd - * - (5 * nthreads) fd's for general purpose thread-use - */ - rlim.rlim_cur = rlim.rlim_max = 3 + nthreads + nthreads * nslots + - 5 * nthreads; - if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { - if (errno == EPERM) { - die("You need to run as root or have " - "CAP_SYS_RESOURCE set, or are asking for more " - "file descriptors than the system can offer"); - } else { - die("setrlimit:"); - } - } - - /* - * create the (non-blocking) listening socket - * - * we could use SO_REUSEPORT and create a listening socket for - * each thread (for better load-balancing, given each thread - * would get his own kernel-queue), but this increases latency - * (as a thread might get stuck on a larger request, making all - * other request wait in line behind it). - * - * socket contention with a single listening socket is a - * non-issue and thread-load-balancing is better fixed in the - * kernel by changing epoll-sheduling from a FIFO- to a - * LIFO-model, especially as it doesn't affect performance - */ - insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) : - sock_get_ips(srv.host, srv.port); - if (sock_set_nonblocking(insock)) { - return 1; - } - - /* - * before dropping privileges, we fork, as we need to remove - * the UNIX-domain socket when we shut down, which we need - * privileges for - */ - switch (fork()) { - case -1: - warn("fork:"); - break; - case 0: - /* restore default handlers */ - handlesignals(SIG_DFL); - - /* reap children automatically */ - if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) { - die("signal: Failed to set SIG_IGN on SIGCHLD"); - } - if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { - die("signal: Failed to set SIG_IGN on SIGPIPE"); - } - - /* - * try increasing the thread-limit by the number - * of threads we need (which is the only reliable - * workaround I know given the thread-limit is per user - * rather than per process), but ignore EPERM errors, - * because this most probably means the user has already - * set the value to the kernel's limit, and there's not - * much we can do in any other case. - * There's also no danger of overflow as the value - * returned by getrlimit() is way below the limits of the - * rlim_t datatype. - */ - if (getrlimit(RLIMIT_NPROC, &rlim) < 0) { - die("getrlimit:"); - } - if (rlim.rlim_max == RLIM_INFINITY) { - if (rlim.rlim_cur != RLIM_INFINITY) { - /* try increasing current limit by nthreads */ - rlim.rlim_cur += nthreads; - } - } else { - /* try increasing current and hard limit by nthreads */ - rlim.rlim_cur = rlim.rlim_max += nthreads; - } - if (setrlimit(RLIMIT_NPROC, &rlim) < 0 && errno != EPERM) { - die("setrlimit()"); - } - - /* limit ourselves to reading the servedir and block further unveils */ - eunveil(servedir, "r"); - eunveil(NULL, NULL); - - /* chroot */ - if (chdir(servedir) < 0) { - die("chdir '%s':", servedir); - } - if (chroot(".") < 0) { - if (errno == EPERM) { - die("You need to run as root or have " - "CAP_SYS_CHROOT set"); - } else { - die("chroot:"); - } - } - - /* drop root */ - if (pwd->pw_uid == 0 || grp->gr_gid == 0) { - die("Won't run under root %s for hopefully obvious reasons", - (pwd->pw_uid == 0) ? (grp->gr_gid == 0) ? - "user and group" : "user" : "group"); - } - - if (setgroups(1, &(grp->gr_gid)) < 0) { - if (errno == EPERM) { - die("You need to run as root or have " - "CAP_SETGID set"); - } else { - die("setgroups:"); - } - } - if (setgid(grp->gr_gid) < 0) { - if (errno == EPERM) { - die("You need to run as root or have " - "CAP_SETGID set"); - } else { - die("setgid:"); - } - - } - if (setuid(pwd->pw_uid) < 0) { - if (errno == EPERM) { - die("You need to run as root or have " - "CAP_SETUID set"); - } else { - die("setuid:"); - } - } - - if (udsname) { - epledge("stdio rpath proc unix", NULL); - } else { - epledge("stdio rpath proc inet", NULL); - } - - /* accept incoming connections */ - server_init_thread_pool(insock, nthreads, nslots, &srv); - - exit(0); - default: - /* limit ourselves even further while we are waiting */ - if (udsname) { - eunveil(udsname, "c"); - eunveil(NULL, NULL); - epledge("stdio cpath", NULL); - } else { - eunveil("/", ""); - eunveil(NULL, NULL); - epledge("stdio", NULL); - } - - while (wait(&status) > 0) - ; - } - - cleanup(); - return status; -} diff --git a/.local/src/quark/quark.1 b/.local/src/quark/quark.1 deleted file mode 100644 index d752cc73..00000000 --- a/.local/src/quark/quark.1 +++ /dev/null @@ -1,137 +0,0 @@ -.Dd 2020-09-27 -.Dt QUARK 1 -.Os suckless.org -.Sh NAME -.Nm quark -.Nd simple static web server -.Sh SYNOPSIS -.Nm -.Fl p Ar port -.Op Fl h Ar host -.Op Fl u Ar user -.Op Fl g Ar group -.Op Fl s Ar num -.Op Fl t Ar num -.Op Fl d Ar dir -.Op Fl l -.Op Fl i Ar file -.Oo Fl v Ar vhost Oc ... -.Oo Fl m Ar map Oc ... -.Nm -.Fl U Ar file -.Op Fl p Ar port -.Op Fl u Ar user -.Op Fl g Ar group -.Op Fl s Ar num -.Op Fl t Ar num -.Op Fl d Ar dir -.Op Fl l -.Op Fl i Ar file -.Oo Fl v Ar vhost Oc ... -.Oo Fl m Ar map Oc ... -.Sh DESCRIPTION -.Nm -is a simple HTTP GET/HEAD-only web server for static content. -It supports virtual hosts (see -.Fl v ) , -explicit redirects (see -.Fl m ) , -directory listings (see -.Fl l ) , -conditional "If-Modified-Since"-requests (RFC 7232), range requests -(RFC 7233) and well-known URIs (RFC 8615), while refusing to serve -hidden files and directories. -.Sh OPTIONS -.Bl -tag -width Ds -.It Fl d Ar dir -Serve -.Ar dir -after chrooting into it. -The default is ".". -.It Fl g Ar group -Set group ID when dropping privileges, and in socket mode the group of the -socket file, to the ID of -.Ar group . -The default is "nogroup". -.It Fl h Ar host -Use -.Ar host -as the server hostname. -The default is the loopback interface (i.e. localhost). -.It Fl i Ar file -Set -.Ar file -as the directory index. -The default is "index.html". -.It Fl l -Enable directory listing. -.It Fl m Ar map -Add the URI prefix mapping rule specified by -.Ar map , -which has the form -.Qq Pa from to [chost] , -where each element is separated with spaces (0x20) that can be -escaped with '\\'. -.Pp -The prefix -.Pa from -of all matching URIs is replaced with -.Pa to , -optionally limited to the canonical virtual host -.Pa chost . -If no virtual hosts are given, -.Pa chost -is ignored. -.It Fl p Ar port -In host mode, listen on port -.Ar port -for incoming connections. -In socket mode, use -.Ar port -for constructing proper virtual host -redirects on non-standard ports. -.It Fl U Ar file -Create the UNIX-domain socket -.Ar file , -listen on it for incoming connections and remove it on exit. -.It Fl s Ar num -Set the number of connection slots per worker thread to -.Ar num . -The default is 64. -.It Fl t Ar num -Set the number of worker threads to -.Ar num . -The default is 4. -.It Fl u Ar user -Set user ID when dropping privileges, -and in socket mode the user of the socket file, -to the ID of -.Ar user . -The default is "nobody". -.It Fl v Ar vhost -Add the virtual host specified by -.Ar vhost , -which has the form -.Qq Pa chost regex dir [prefix] , -where each element is separated with spaces (0x20) that can be -escaped with '\\'. -.Pp -A request matching the virtual host regular expression -.Pa regex -(see -.Xr regex 3 ) -is redirected to the canonical host -.Pa chost , -if they differ, using the directory -.Pa dir -as the root directory, optionally prefixing the URI with -.Pa prefix . -If any virtual hosts are specified, all requests on non-matching -hosts are discarded. -.El -.Sh CUSTOMIZATION -.Nm -can be customized by creating a custom config.h from config.def.h and -(re)compiling the source code. This keeps it fast, secure and simple. -.Sh AUTHORS -.An Laslo Hunhold Aq Mt dev@frign.de diff --git a/.local/src/quark/queue.c b/.local/src/quark/queue.c deleted file mode 100644 index 8f75f92f..00000000 --- a/.local/src/quark/queue.c +++ /dev/null @@ -1,217 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include - -#ifdef __linux__ - #include -#else - #include - #include - #include -#endif - -#include "queue.h" -#include "util.h" - -int -queue_create(void) -{ - int qfd; - - #ifdef __linux__ - if ((qfd = epoll_create1(0)) < 0) { - warn("epoll_create1:"); - } - #else - if ((qfd = kqueue()) < 0) { - warn("kqueue:"); - } - #endif - - return qfd; -} - -int -queue_add_fd(int qfd, int fd, enum queue_event_type t, int shared, - const void *data) -{ - #ifdef __linux__ - struct epoll_event e; - - /* set event flag */ - if (shared) { - /* - * if the fd is shared, "exclusive" is the only - * way to avoid spurious wakeups and "blocking" - * accept()'s. - */ - e.events = EPOLLEXCLUSIVE; - } else { - /* - * if we have the fd for ourselves (i.e. only - * within the thread), we want to be - * edge-triggered, as our logic makes sure - * that the buffers are drained when we return - * to epoll_wait() - */ - e.events = EPOLLET; - } - - switch (t) { - case QUEUE_EVENT_IN: - e.events |= EPOLLIN; - break; - case QUEUE_EVENT_OUT: - e.events |= EPOLLOUT; - break; - } - - /* set data pointer */ - e.data.ptr = (void *)data; - - /* register fd in the interest list */ - if (epoll_ctl(qfd, EPOLL_CTL_ADD, fd, &e) < 0) { - warn("epoll_ctl:"); - return -1; - } - #else - struct kevent e; - int events; - - /* prepare event flag */ - events = (shared) ? 0 : EV_CLEAR; - - switch (t) { - case QUEUE_EVENT_IN: - events |= EVFILT_READ; - break; - case QUEUE_EVENT_OUT: - events |= EVFILT_WRITE; - break; - } - - EV_SET(&e, fd, events, EV_ADD, 0, 0, (void *)data); - - if (kevent(qfd, &e, 1, NULL, 0, NULL) < 0) { - warn("kevent:"); - return -1; - } - #endif - - return 0; -} - -int -queue_mod_fd(int qfd, int fd, enum queue_event_type t, const void *data) -{ - #ifdef __linux__ - struct epoll_event e; - - /* set event flag (only for non-shared fd's) */ - e.events = EPOLLET; - - switch (t) { - case QUEUE_EVENT_IN: - e.events |= EPOLLIN; - break; - case QUEUE_EVENT_OUT: - e.events |= EPOLLOUT; - break; - } - - /* set data pointer */ - e.data.ptr = (void *)data; - - /* register fd in the interest list */ - if (epoll_ctl(qfd, EPOLL_CTL_MOD, fd, &e) < 0) { - warn("epoll_ctl:"); - return -1; - } - #else - struct kevent e; - int events; - - events = EV_CLEAR; - - switch (t) { - case QUEUE_EVENT_IN: - events |= EVFILT_READ; - break; - case QUEUE_EVENT_OUT: - events |= EVFILT_WRITE; - break; - } - - EV_SET(&e, fd, events, EV_ADD, 0, 0, (void *)data); - - if (kevent(qfd, &e, 1, NULL, 0, NULL) < 0) { - warn("kevent:"); - return -1; - } - #endif - - return 0; -} - -int -queue_rem_fd(int qfd, int fd) -{ - #ifdef __linux__ - struct epoll_event e; - - if (epoll_ctl(qfd, EPOLL_CTL_DEL, fd, &e) < 0) { - warn("epoll_ctl:"); - return -1; - } - #else - struct kevent e; - - EV_SET(&e, fd, 0, EV_DELETE, 0, 0, 0); - - if (kevent(qfd, &e, 1, NULL, 0, NULL) < 0) { - warn("kevent:"); - return -1; - } - #endif - - return 0; -} - -ssize_t -queue_wait(int qfd, queue_event *e, size_t elen) -{ - ssize_t nready; - - #ifdef __linux__ - if ((nready = epoll_wait(qfd, e, elen, -1)) < 0) { - warn("epoll_wait:"); - return -1; - } - #else - if ((nready = kevent(qfd, NULL, 0, e, elen, NULL)) < 0) { - warn("kevent:"); - return -1; - } - #endif - - return nready; -} - -void * -queue_event_get_data(const queue_event *e) -{ - #ifdef __linux__ - return e->data.ptr; - #else - return e->udata; - #endif -} - -int -queue_event_is_error(const queue_event *e) -{ - #ifdef __linux__ - return (e->events & ~(EPOLLIN | EPOLLOUT)) ? 1 : 0; - #else - return (e->flags & EV_EOF) ? 1 : 0; - #endif -} diff --git a/.local/src/quark/queue.h b/.local/src/quark/queue.h deleted file mode 100644 index e31f7d2c..00000000 --- a/.local/src/quark/queue.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef QUEUE_H -#define QUEUE_H - -#include - -#ifdef __linux__ - #include - - typedef struct epoll_event queue_event; -#else - #include - #include - #include - - typedef struct kevent queue_event; -#endif - -enum queue_event_type { - QUEUE_EVENT_IN, - QUEUE_EVENT_OUT, -}; - -int queue_create(void); -int queue_add_fd(int, int, enum queue_event_type, int, const void *); -int queue_mod_fd(int, int, enum queue_event_type, const void *); -int queue_rem_fd(int, int); -ssize_t queue_wait(int, queue_event *, size_t); - -void *queue_event_get_data(const queue_event *); - -int queue_event_is_error(const queue_event *e); - -#endif /* QUEUE_H */ diff --git a/.local/src/quark/server.c b/.local/src/quark/server.c deleted file mode 100644 index fc8cb37c..00000000 --- a/.local/src/quark/server.c +++ /dev/null @@ -1,177 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include - -#include "connection.h" -#include "queue.h" -#include "server.h" -#include "util.h" - -struct worker_data { - int insock; - size_t nslots; - const struct server *srv; -}; - -static void * -server_worker(void *data) -{ - queue_event *event = NULL; - struct connection *connection, *c, *newc; - struct worker_data *d = (struct worker_data *)data; - int qfd; - ssize_t nready; - size_t i; - - /* allocate connections */ - if (!(connection = calloc(d->nslots, sizeof(*connection)))) { - die("calloc:"); - } - - /* create event queue */ - if ((qfd = queue_create()) < 0) { - exit(1); - } - - /* add insock to the interest list (with data=NULL) */ - if (queue_add_fd(qfd, d->insock, QUEUE_EVENT_IN, 1, NULL) < 0) { - exit(1); - } - - /* allocate event array */ - if (!(event = reallocarray(event, d->nslots, sizeof(*event)))) { - die("reallocarray:"); - } - - for (;;) { - /* wait for new activity */ - if ((nready = queue_wait(qfd, event, d->nslots)) < 0) { - exit(1); - } - - /* handle events */ - for (i = 0; i < (size_t)nready; i++) { - c = queue_event_get_data(&event[i]); - - if (queue_event_is_error(&event[i])) { - if (c != NULL) { - queue_rem_fd(qfd, c->fd); - c->res.status = 0; - connection_log(c); - connection_reset(c); - } - - continue; - } - - if (c == NULL) { - /* add new connection to the interest list */ - if (!(newc = connection_accept(d->insock, - connection, - d->nslots))) { - /* - * the socket is either blocking - * or something failed. - * In both cases, we just carry on - */ - continue; - } - - /* - * add event to the interest list - * (we want IN, because we start - * with receiving the header) - */ - if (queue_add_fd(qfd, newc->fd, - QUEUE_EVENT_IN, - 0, newc) < 0) { - /* not much we can do here */ - continue; - } - } else { - /* serve existing connection */ - connection_serve(c, d->srv); - - if (c->fd == 0) { - /* we are done */ - memset(c, 0, sizeof(struct connection)); - continue; - } - - /* - * rearm the event based on the state - * we are "stuck" at - */ - switch(c->state) { - case C_RECV_HEADER: - if (queue_mod_fd(qfd, c->fd, - QUEUE_EVENT_IN, - c) < 0) { - connection_reset(c); - break; - } - break; - case C_SEND_HEADER: - case C_SEND_BODY: - if (queue_mod_fd(qfd, c->fd, - QUEUE_EVENT_OUT, - c) < 0) { - connection_reset(c); - break; - } - break; - default: - break; - } - } - } - } - - return NULL; -} - -void -server_init_thread_pool(int insock, size_t nthreads, size_t nslots, - const struct server *srv) -{ - pthread_t *thread = NULL; - struct worker_data *d = NULL; - size_t i; - - /* allocate worker_data structs */ - if (!(d = reallocarray(d, nthreads, sizeof(*d)))) { - die("reallocarray:"); - } - for (i = 0; i < nthreads; i++) { - d[i].insock = insock; - d[i].nslots = nslots; - d[i].srv = srv; - } - - /* allocate and initialize thread pool */ - if (!(thread = reallocarray(thread, nthreads, sizeof(*thread)))) { - die("reallocarray:"); - } - for (i = 0; i < nthreads; i++) { - if (pthread_create(&thread[i], NULL, server_worker, &d[i]) != 0) { - if (errno == EAGAIN) { - die("You need to run as root or have " - "CAP_SYS_RESOURCE set, or are trying " - "to create more threads than the " - "system can offer"); - } else { - die("pthread_create:"); - } - } - } - - /* wait for threads */ - for (i = 0; i < nthreads; i++) { - if ((errno = pthread_join(thread[i], NULL))) { - warn("pthread_join:"); - } - } -} diff --git a/.local/src/quark/server.h b/.local/src/quark/server.h deleted file mode 100644 index 7bbcd762..00000000 --- a/.local/src/quark/server.h +++ /dev/null @@ -1,35 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef SERVER_H -#define SERVER_H - -#include -#include - -struct vhost { - char *chost; - char *regex; - char *dir; - char *prefix; - regex_t re; -}; - -struct map { - char *chost; - char *from; - char *to; -}; - -struct server { - char *host; - char *port; - char *docindex; - int listdirs; - struct vhost *vhost; - size_t vhost_len; - struct map *map; - size_t map_len; -}; - -void server_init_thread_pool(int, size_t, size_t, const struct server *); - -#endif /* SERVER_H */ diff --git a/.local/src/quark/sock.c b/.local/src/quark/sock.c deleted file mode 100644 index ecb73ef5..00000000 --- a/.local/src/quark/sock.c +++ /dev/null @@ -1,209 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sock.h" -#include "util.h" - -int -sock_get_ips(const char *host, const char* port) -{ - struct addrinfo hints = { - .ai_flags = AI_NUMERICSERV, - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - }; - struct addrinfo *ai, *p; - int ret, insock = 0; - - if ((ret = getaddrinfo(host, port, &hints, &ai))) { - die("getaddrinfo: %s", gai_strerror(ret)); - } - - for (p = ai; p; p = p->ai_next) { - if ((insock = socket(p->ai_family, p->ai_socktype, - p->ai_protocol)) < 0) { - continue; - } - if (setsockopt(insock, SOL_SOCKET, SO_REUSEADDR, - &(int){1}, sizeof(int)) < 0) { - die("setsockopt:"); - } - if (bind(insock, p->ai_addr, p->ai_addrlen) < 0) { - /* bind failed, close the insock and retry */ - if (close(insock) < 0) { - die("close:"); - } - continue; - } - break; - } - freeaddrinfo(ai); - if (!p) { - /* we exhaustet the addrinfo-list and found no connection */ - if (errno == EACCES) { - die("You need to run as root or have " - "CAP_NET_BIND_SERVICE set to bind to " - "privileged ports"); - } else { - die("bind:"); - } - } - - if (listen(insock, SOMAXCONN) < 0) { - die("listen:"); - } - - return insock; -} - -int -sock_get_uds(const char *udsname, uid_t uid, gid_t gid) -{ - struct sockaddr_un addr = { - .sun_family = AF_UNIX, - }; - size_t udsnamelen; - int insock, sockmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | - S_IROTH | S_IWOTH; - - if ((insock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - die("socket:"); - } - - if ((udsnamelen = strlen(udsname)) > sizeof(addr.sun_path) - 1) { - die("UNIX-domain socket name truncated"); - } - memcpy(addr.sun_path, udsname, udsnamelen + 1); - - if (bind(insock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { - die("bind '%s':", udsname); - } - - if (listen(insock, SOMAXCONN) < 0) { - sock_rem_uds(udsname); - die("listen:"); - } - - if (chmod(udsname, sockmode) < 0) { - sock_rem_uds(udsname); - die("chmod '%s':", udsname); - } - - if (chown(udsname, uid, gid) < 0) { - sock_rem_uds(udsname); - die("chown '%s':", udsname); - } - - return insock; -} - -void -sock_rem_uds(const char *udsname) -{ - if (unlink(udsname) < 0) { - die("unlink '%s':", udsname); - } -} - -int -sock_set_timeout(int fd, int sec) -{ - struct timeval tv; - - tv.tv_sec = sec; - tv.tv_usec = 0; - - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 || - setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { - warn("setsockopt:"); - return 1; - } - - return 0; -} - -int -sock_set_nonblocking(int fd) -{ - int flags; - - if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { - warn("fcntl:"); - return 1; - } - - flags |= O_NONBLOCK; - - if (fcntl(fd, F_SETFL, flags) < 0) { - warn("fcntl:"); - return 1; - } - - return 0; -} - -int -sock_get_inaddr_str(const struct sockaddr_storage *in_sa, char *str, - size_t len) -{ - switch (in_sa->ss_family) { - case AF_INET: - if (!inet_ntop(AF_INET, - &(((struct sockaddr_in *)in_sa)->sin_addr), - str, len)) { - warn("inet_ntop:"); - return 1; - } - break; - case AF_INET6: - if (!inet_ntop(AF_INET6, - &(((struct sockaddr_in6 *)in_sa)->sin6_addr), - str, len)) { - warn("inet_ntop:"); - return 1; - } - break; - case AF_UNIX: - snprintf(str, len, "uds"); - break; - default: - snprintf(str, len, "-"); - } - - return 0; -} - -int -sock_same_addr(const struct sockaddr_storage *sa1, const struct sockaddr_storage *sa2) -{ - /* return early if address-families don't match */ - if (sa1->ss_family != sa2->ss_family) { - return 0; - } - - switch (sa1->ss_family) { - case AF_INET6: - return memcmp(((struct sockaddr_in6 *)sa1)->sin6_addr.s6_addr, - ((struct sockaddr_in6 *)sa2)->sin6_addr.s6_addr, - sizeof(((struct sockaddr_in6 *)sa1)->sin6_addr.s6_addr)); - case AF_INET: - return ntohl(((struct sockaddr_in *)sa1)->sin_addr.s_addr) == - ntohl(((struct sockaddr_in *)sa2)->sin_addr.s_addr); - default: /* AF_UNIX */ - return strcmp(((struct sockaddr_un *)sa1)->sun_path, - ((struct sockaddr_un *)sa2)->sun_path) == 0; - } -} diff --git a/.local/src/quark/sock.h b/.local/src/quark/sock.h deleted file mode 100644 index 22ae3039..00000000 --- a/.local/src/quark/sock.h +++ /dev/null @@ -1,18 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef SOCK_H -#define SOCK_H - -#include -#include -#include - -int sock_get_ips(const char *, const char *); -int sock_get_uds(const char *, uid_t, gid_t); -void sock_rem_uds(const char *); -int sock_set_timeout(int, int); -int sock_set_nonblocking(int); -int sock_get_inaddr_str(const struct sockaddr_storage *, char *, size_t); -int sock_same_addr(const struct sockaddr_storage *, - const struct sockaddr_storage *); - -#endif /* SOCK_H */ diff --git a/.local/src/quark/util.c b/.local/src/quark/util.c deleted file mode 100644 index f10e546c..00000000 --- a/.local/src/quark/util.c +++ /dev/null @@ -1,281 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __OpenBSD__ -#include -#endif /* __OpenBSD__ */ - -#include "util.h" - -char *argv0; - -static void -verr(const char *fmt, va_list ap) -{ - if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) { - fprintf(stderr, "%s: ", argv0); - } - - vfprintf(stderr, fmt, ap); - - if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { - fputc(' ', stderr); - perror(NULL); - } else { - fputc('\n', stderr); - } -} - -void -warn(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - verr(fmt, ap); - va_end(ap); -} - -void -die(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - verr(fmt, ap); - va_end(ap); - - exit(1); -} - -void -epledge(const char *promises, const char *execpromises) -{ - (void)promises; - (void)execpromises; - -#ifdef __OpenBSD__ - if (pledge(promises, execpromises) == -1) { - die("pledge:"); - } -#endif /* __OpenBSD__ */ -} - -void -eunveil(const char *path, const char *permissions) -{ - (void)path; - (void)permissions; - -#ifdef __OpenBSD__ - if (unveil(path, permissions) == -1) { - die("unveil:"); - } -#endif /* __OpenBSD__ */ -} - -int -timestamp(char *buf, size_t len, time_t t) -{ - struct tm tm; - - if (gmtime_r(&t, &tm) == NULL || - strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) { - return 1; - } - - return 0; -} - -int -esnprintf(char *str, size_t size, const char *fmt, ...) -{ - va_list ap; - int ret; - - va_start(ap, fmt); - ret = vsnprintf(str, size, fmt, ap); - va_end(ap); - - return (ret < 0 || (size_t)ret >= size); -} - -int -prepend(char *str, size_t size, const char *prefix) -{ - size_t len = strlen(str), prefixlen = strlen(prefix); - - if (len + prefixlen + 1 > size) { - return 1; - } - - memmove(str + prefixlen, str, len + 1); - memcpy(str, prefix, prefixlen); - - return 0; -} - -int -spacetok(const char *s, char **t, size_t tlen) -{ - const char *tok; - size_t i, j, toki, spaces; - - /* fill token-array with NULL-pointers */ - for (i = 0; i < tlen; i++) { - t[i] = NULL; - } - toki = 0; - - /* don't allow NULL string or leading spaces */ - if (!s || *s == ' ') { - return 1; - } -start: - /* skip spaces */ - for (; *s == ' '; s++) - ; - - /* don't allow trailing spaces */ - if (*s == '\0') { - goto err; - } - - /* consume token */ - for (tok = s, spaces = 0; ; s++) { - if (*s == '\\' && *(s + 1) == ' ') { - spaces++; - s++; - continue; - } else if (*s == ' ') { - /* end of token */ - goto token; - } else if (*s == '\0') { - /* end of string */ - goto token; - } - } -token: - if (toki >= tlen) { - goto err; - } - if (!(t[toki] = malloc(s - tok - spaces + 1))) { - die("malloc:"); - } - for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) { - if (tok[i] == '\\' && tok[i + 1] == ' ') { - i++; - } - t[toki][j] = tok[i]; - } - t[toki][s - tok - spaces] = '\0'; - toki++; - - if (*s == ' ') { - s++; - goto start; - } - - return 0; -err: - for (i = 0; i < tlen; i++) { - free(t[i]); - t[i] = NULL; - } - - return 1; -} - - - -#define INVALID 1 -#define TOOSMALL 2 -#define TOOLARGE 3 - -long long -strtonum(const char *numstr, long long minval, long long maxval, - const char **errstrp) -{ - long long ll = 0; - int error = 0; - char *ep; - struct errval { - const char *errstr; - int err; - } ev[4] = { - { NULL, 0 }, - { "invalid", EINVAL }, - { "too small", ERANGE }, - { "too large", ERANGE }, - }; - - ev[0].err = errno; - errno = 0; - if (minval > maxval) { - error = INVALID; - } else { - ll = strtoll(numstr, &ep, 10); - if (numstr == ep || *ep != '\0') - error = INVALID; - else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) - error = TOOSMALL; - else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) - error = TOOLARGE; - } - if (errstrp != NULL) - *errstrp = ev[error].errstr; - errno = ev[error].err; - if (error) - ll = 0; - - return ll; -} - -/* - * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX - * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW - */ -#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) - -void * -reallocarray(void *optr, size_t nmemb, size_t size) -{ - if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && - nmemb > 0 && SIZE_MAX / nmemb < size) { - errno = ENOMEM; - return NULL; - } - return realloc(optr, size * nmemb); -} - -int -buffer_appendf(struct buffer *buf, const char *suffixfmt, ...) -{ - va_list ap; - int ret; - - va_start(ap, suffixfmt); - ret = vsnprintf(buf->data + buf->len, - sizeof(buf->data) - buf->len, suffixfmt, ap); - va_end(ap); - - if (ret < 0 || (size_t)ret >= (sizeof(buf->data) - buf->len)) { - /* truncation occured, discard and error out */ - memset(buf->data + buf->len, 0, - sizeof(buf->data) - buf->len); - return 1; - } - - /* increase buffer length by number of bytes written */ - buf->len += ret; - - return 0; -} diff --git a/.local/src/quark/util.h b/.local/src/quark/util.h deleted file mode 100644 index f327c130..00000000 --- a/.local/src/quark/util.h +++ /dev/null @@ -1,42 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef UTIL_H -#define UTIL_H - -#include -#include -#include - -#include "config.h" - -/* general purpose buffer */ -struct buffer { - char data[BUFFER_SIZE]; - size_t len; -}; - -#undef MIN -#define MIN(x,y) ((x) < (y) ? (x) : (y)) -#undef MAX -#define MAX(x,y) ((x) > (y) ? (x) : (y)) -#undef LEN -#define LEN(x) (sizeof (x) / sizeof *(x)) - -extern char *argv0; - -void warn(const char *, ...); -void die(const char *, ...); - -void epledge(const char *, const char *); -void eunveil(const char *, const char *); - -int timestamp(char *, size_t, time_t); -int esnprintf(char *, size_t, const char *, ...); -int prepend(char *, size_t, const char *); -int spacetok(const char *, char **, size_t); - -void *reallocarray(void *, size_t, size_t); -long long strtonum(const char *, long long, long long, const char **); - -int buffer_appendf(struct buffer *, const char *, ...); - -#endif /* UTIL_H */ diff --git a/.local/src/slock/.ccls-cache/@home@yigit@.local@src@a@s/config.h b/.local/src/slock/.ccls-cache/@home@yigit@.local@src@a@s/config.h deleted file mode 100644 index 04bf416c..00000000 --- a/.local/src/slock/.ccls-cache/@home@yigit@.local@src@a@s/config.h +++ /dev/null @@ -1,21 +0,0 @@ -/* user and group to drop privileges to */ -static const char *user = "nobody"; -static const char *group = "nogroup"; - -static const char *colorname[NUMCOLS] = { - [INIT] = "black", /* after initialization */ - [INPUT] = "#005577", /* during input */ - [FAILED] = "#CC3333", /* wrong password */ -}; - -/* treat a cleared input like a wrong password (color) */ -static const int failonclear = 1; - -/*Enable blur*/ -//#define BLUR -/*Set blur radius*/ -static const int blurRadius=5; -/*Enable Pixelation*/ -#define PIXELATION -/*Set pixelation radius*/ -static const int pixelSize=6; diff --git a/.local/src/slock/.ccls-cache/@home@yigit@.local@src@a@s/config.h.blob b/.local/src/slock/.ccls-cache/@home@yigit@.local@src@a@s/config.h.blob deleted file mode 100644 index 700af5e3..00000000 Binary files a/.local/src/slock/.ccls-cache/@home@yigit@.local@src@a@s/config.h.blob and /dev/null differ diff --git a/.local/src/slock/config.h b/.local/src/slock/config.h index 28098614..ef029c88 100644 --- a/.local/src/slock/config.h +++ b/.local/src/slock/config.h @@ -3,13 +3,13 @@ static const char *user = "nobody"; static const char *group = "nogroup"; static const char *colorname[NUMCOLS] = { - [INIT] = "black", /* after initialization */ + [INIT] = "#black", /* after initialization */ [INPUT] = "#005577", /* during input */ [FAILED] = "#CC3333", /* wrong password */ }; /* treat a cleared input like a wrong password (color) */ -static const int failonclear = 1; +static const int failonclear = 0; /*Enable blur*/ //#define BLUR @@ -19,3 +19,22 @@ static const int blurRadius=5; #define PIXELATION /*Set pixelation radius*/ static const int pixelSize=10; + +/* insert grid pattern with scale 1:1, the size can be changed with logosize */ +static const int logosize = 75; +static const int logow = 12; /* grid width and height for right center alignment*/ +static const int logoh = 6; + +static XRectangle rectangles[9] = { + /* x y w h */ + { 0, 3, 1, 3 }, + { 1, 3, 2, 1 }, + { 0, 5, 8, 1 }, + { 3, 0, 1, 5 }, + { 5, 3, 1, 2 }, + { 7, 3, 1, 2 }, + { 8, 3, 4, 1 }, + { 9, 4, 1, 2 }, + { 11, 4, 1, 2 }, + +}; diff --git a/.local/src/slock/config.mk b/.local/src/slock/config.mk index d0c2f010..c7d8ea7f 100644 --- a/.local/src/slock/config.mk +++ b/.local/src/slock/config.mk @@ -10,12 +10,20 @@ MANPREFIX = ${PREFIX}/share/man X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lXft +FREETYPEINC = /usr/include/freetype2 + # includes and libs -INCS = -I. -I/usr/include -I${X11INC} -LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lXext -lXrandr -lImlib2 +INCS = -I. -I/usr/include -I${X11INC} -I${FREETYPEINC} +LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXext -lXrandr -lImlib2 # flags -CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H ${XINERAMAFLAGS} CFLAGS = -std=c99 -pedantic -Wall -Ofast ${INCS} ${CPPFLAGS} LDFLAGS = -s ${LIBS} COMPATSRC = explicit_bzero.c diff --git a/.local/src/slock/slock b/.local/src/slock/slock index 154861f3..1b014f57 100755 Binary files a/.local/src/slock/slock and b/.local/src/slock/slock differ diff --git a/.local/src/slock/slock.c b/.local/src/slock/slock.c index 1a4d6e31..4255148c 100644 --- a/.local/src/slock/slock.c +++ b/.local/src/slock/slock.c @@ -1,5 +1,6 @@ /* See LICENSE file for license details. */ #define _XOPEN_SOURCE 500 +#define LENGTH(X) (sizeof X / sizeof X[0]) #if HAVE_SHADOW_H #include #endif @@ -15,9 +16,13 @@ #include #include #include +#ifdef XINERAMA +#include +#endif #include #include #include +#include #include #include "arg.h" @@ -32,12 +37,19 @@ enum { NUMCOLS }; +#include "config.h" + struct lock { int screen; Window root, win; Pixmap pmap; Pixmap bgmap; unsigned long colors[NUMCOLS]; + unsigned int x, y; + unsigned int xoff, yoff, mw, mh; + Drawable drawable; + GC gc; + XRectangle rectangles[LENGTH(rectangles)]; }; struct xrandr { @@ -46,8 +58,6 @@ struct xrandr { int errbase; }; -#include "config.h" - Imlib_Image image; static void @@ -61,6 +71,30 @@ die(const char *errstr, ...) exit(1); } +static void +resizerectangles(struct lock *lock) +{ + int i; + + for (i = 0; i < LENGTH(rectangles); i++){ + lock->rectangles[i].x = (rectangles[i].x * logosize) + + lock->xoff + ((lock->mw) / 2) - (logow / 2 * logosize); + lock->rectangles[i].y = (rectangles[i].y * logosize) + + lock->yoff + ((lock->mh) / 2) - (logoh / 2 * logosize); + lock->rectangles[i].width = rectangles[i].width * logosize; + lock->rectangles[i].height = rectangles[i].height * logosize; + } +} + +static void +drawlogo(Display *dpy, struct lock *lock, int color) +{ + XSetForeground(dpy, lock->gc, lock->colors[color]); + XFillRectangles(dpy, lock->win, lock->gc, lock->rectangles, LENGTH(rectangles)); + XSync(dpy, False); +} + + #ifdef __linux__ #include #include @@ -194,11 +228,15 @@ readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens, color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT); if (running && oldc != color) { for (screen = 0; screen < nscreens; screen++) { - if(locks[screen]->bgmap) + if(locks[screen]->bgmap){ XSetWindowBackgroundPixmap(dpy, locks[screen]->win, locks[screen]->bgmap); + } else XSetWindowBackground(dpy, locks[screen]->win, locks[screen]->colors[0]); + XClearWindow(dpy, locks[screen]->win); + if ( len || failure || failonclear) + drawlogo(dpy, locks[screen], color); } oldc = color; } @@ -233,14 +271,37 @@ lockscreen(Display *dpy, struct xrandr *rr, int screen) XColor color, dummy; XSetWindowAttributes wa; Cursor invisible; + #ifdef XINERAMA + XineramaScreenInfo *info; + int n; + #endif if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock)))) return NULL; lock->screen = screen; lock->root = RootWindow(dpy, lock->screen); + lock->x = DisplayWidth(dpy, lock->screen); + lock->y = DisplayHeight(dpy, lock->screen); +#ifdef XINERAMA + if ((info = XineramaQueryScreens(dpy, &n))) { + lock->xoff = info[0].x_org; + lock->yoff = info[0].y_org; + lock->mw = info[0].width; + lock->mh = info[0].height; + } else +#endif + { + lock->xoff = lock->yoff = 0; + lock->mw = lock->x; + lock->mh = lock->y; + } + lock->drawable = XCreatePixmap(dpy, lock->root, + lock->x, lock->y, DefaultDepth(dpy, screen)); + lock->gc = XCreateGC(dpy, lock->root, 0, NULL); + XSetLineAttributes(dpy, lock->gc, 1, LineSolid, CapButt, JoinMiter); - if(image) + if(image) { lock->bgmap = XCreatePixmap(dpy, lock->root, DisplayWidth(dpy, lock->screen), DisplayHeight(dpy, lock->screen), DefaultDepth(dpy, lock->screen)); imlib_context_set_image(image); @@ -274,6 +335,8 @@ lockscreen(Display *dpy, struct xrandr *rr, int screen) &color, &color, 0, 0); XDefineCursor(dpy, lock->win, invisible); + resizerectangles(lock); + /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */ for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) { if (ptgrab != GrabSuccess) { @@ -379,20 +442,20 @@ main(int argc, char **argv) { imlib_context_set_image(image); imlib_context_set_display(dpy); imlib_context_set_visual(DefaultVisual(dpy,0)); - imlib_context_set_drawable(RootWindow(dpy,XScreenNumberOfScreen(scr))); + imlib_context_set_drawable(RootWindow(dpy,XScreenNumberOfScreen(scr))); imlib_copy_drawable_to_image(0,0,0,scr->width,scr->height,0,0,1); #ifdef BLUR /*Blur function*/ imlib_image_blur(blurRadius); -#endif // BLUR +#endif // BLUR #ifdef PIXELATION /*Pixelation*/ int width = scr->width; int height = scr->height; - + for(int y = 0; y < height; y += pixelSize) { for(int x = 0; x < width; x += pixelSize) @@ -401,7 +464,7 @@ main(int argc, char **argv) { int green = 0; int blue = 0; - Imlib_Color pixel; + Imlib_Color pixel; Imlib_Color* pp; pp = &pixel; for(int j = 0; j < pixelSize && j < height; j++) @@ -424,8 +487,8 @@ main(int argc, char **argv) { blue = 0; } } - - + + #endif /* check for Xrandr support */ rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase); @@ -463,5 +526,13 @@ main(int argc, char **argv) { /* everything is now blank. Wait for the correct password */ readpw(dpy, &rr, locks, nscreens, hash); + for (nlocks = 0, s = 0; s < nscreens; s++) { + XFreePixmap(dpy, locks[s]->drawable); + XFreeGC(dpy, locks[s]->gc); + } + + XSync(dpy, 0); + XCloseDisplay(dpy); + return 0; } diff --git a/.local/src/slock/slock.o b/.local/src/slock/slock.o index e86e6551..3dd25e72 100644 Binary files a/.local/src/slock/slock.o and b/.local/src/slock/slock.o differ diff --git a/.profile b/.profile index 0492ec15..f730c47a 100755 --- a/.profile +++ b/.profile @@ -33,6 +33,7 @@ export NPM_CONFIG_USERCONFIG=$XDG_CONFIG_HOME/npm/npmrc export NVM_DIR="$XDG_DATA_HOME"/nvm export GNUPGHOME="$XDG_DATA_HOME"/gnupg export MBSYNCRC="$XDG_CONFIG_HOME"/isync/mbsyncrc +export IMAPFILTER_CONFIG="$XDG_CONFIG_HOME/imapfilter/config.lua" export VIMINIT="set nocp | source ${XDG_CONFIG_HOME:-$HOME/.config}/vim/vimrc" export MYSQL_HISTFILE="$XDG_DATA_HOME"/mysql_history export TASKDATA="$XDG_DATA_HOME"/task