@ -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") |
@ -1 +0,0 @@ | |||||
autocmd! FileType c,cpp,java,php call CSyntaxAfter() |
@ -0,0 +1 @@ | |||||
darkhttpd |
@ -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 |
@ -1,27 +0,0 @@ | |||||
ISC-License | |||||
Copyright 2016-2021 Laslo Hunhold <dev@frign.de> | |||||
Copyright 2004 Ted Unangst <tedu@openbsd.org> | |||||
Copyright 2004 Todd C. Miller <Todd.Miller@courtesan.com> | |||||
Copyright 2008 Otto Moerbeek <otto@drijf.net> | |||||
Copyright 2017-2018 Hiltjo Posthuma <hiltjo@codemadness.org> | |||||
Copyright 2017-2021 Quentin Rameau <quinq@fifth.space> | |||||
Copyright 2018 Josuah Demangeon <mail@josuah.net> | |||||
Copyright 2018 Dominik Schmidt <domischmidt@swissonline.ch> | |||||
Copyright 2018 Aaron Burrow <burrows@charstarstar.com> | |||||
Copyright 2020 Nihal Jere <nihal@nihaljere.xyz> | |||||
Copyright 2020 Rainer Holzner <rholzner@web.de> | |||||
Copyright 2020 Jeremy Bobbin <jer@jer.cx> | |||||
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. |
@ -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" |
@ -1,50 +0,0 @@ | |||||
/* | |||||
* ISC-License | |||||
* | |||||
* Copyright 2004-2017 Christoph Lohmann <20h@r-36.net> | |||||
* Copyright 2017-2018 Laslo Hunhold <dev@frign.de> | |||||
* | |||||
* 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 |
@ -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 */ |
@ -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 |
@ -1,314 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <errno.h> | |||||
#include <netinet/in.h> | |||||
#include <stdio.h> | |||||
#include <string.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/types.h> | |||||
#include <time.h> | |||||
#include <unistd.h> | |||||
#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; | |||||
} |
@ -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 */ |
@ -1,231 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <dirent.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <sys/stat.h> | |||||
#include <time.h> | |||||
#include <unistd.h> | |||||
#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, | |||||
"<!DOCTYPE html>\n<html>\n\t<head>" | |||||
"<title>Index of %s</title></head>\n" | |||||
"\t<body>\n\t\t<a href=\"..\">..</a>", | |||||
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, | |||||
"<br />\n\t\t<a href=\"%s%s\">%s%s</a>", | |||||
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</body>\n</html>\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, | |||||
"<!DOCTYPE html>\n<html>\n\t<head>\n" | |||||
"\t\t<title>%d %s</title>\n\t</head>\n" | |||||
"\t<body>\n\t\t<h1>%d %s</h1>\n" | |||||
"\t</body>\n</html>\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; | |||||
} |
@ -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 */ |
@ -1,97 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#ifndef HTTP_H | |||||
#define HTTP_H | |||||
#include <limits.h> | |||||
#include <sys/socket.h> | |||||
#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 */ |
@ -1,364 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <errno.h> | |||||
#include <grp.h> | |||||
#include <limits.h> | |||||
#include <pwd.h> | |||||
#include <regex.h> | |||||
#include <signal.h> | |||||
#include <stddef.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <sys/resource.h> | |||||
#include <sys/time.h> | |||||
#include <sys/types.h> | |||||
#include <sys/wait.h> | |||||
#include <unistd.h> | |||||
#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; | |||||
} |
@ -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 |
@ -1,217 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <stddef.h> | |||||
#ifdef __linux__ | |||||
#include <sys/epoll.h> | |||||
#else | |||||
#include <sys/types.h> | |||||
#include <sys/event.h> | |||||
#include <sys/time.h> | |||||
#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 | |||||
} |
@ -1,33 +0,0 @@ | |||||
#ifndef QUEUE_H | |||||
#define QUEUE_H | |||||
#include <stddef.h> | |||||
#ifdef __linux__ | |||||
#include <sys/epoll.h> | |||||
typedef struct epoll_event queue_event; | |||||
#else | |||||
#include <sys/types.h> | |||||
#include <sys/event.h> | |||||
#include <sys/time.h> | |||||
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 */ |
@ -1,177 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <errno.h> | |||||
#include <pthread.h> | |||||
#include <stddef.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#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:"); | |||||
} | |||||
} | |||||
} |
@ -1,35 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#ifndef SERVER_H | |||||
#define SERVER_H | |||||
#include <regex.h> | |||||
#include <stddef.h> | |||||
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 */ |
@ -1,209 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <arpa/inet.h> | |||||
#include <errno.h> | |||||
#include <fcntl.h> | |||||
#include <netdb.h> | |||||
#include <netinet/in.h> | |||||
#include <stddef.h> | |||||
#include <stdio.h> | |||||
#include <string.h> | |||||
#include <sys/types.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/time.h> | |||||
#include <sys/un.h> | |||||
#include <unistd.h> | |||||
#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; | |||||
} | |||||
} |
@ -1,18 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#ifndef SOCK_H | |||||
#define SOCK_H | |||||
#include <stddef.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/types.h> | |||||
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 */ |
@ -1,281 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <errno.h> | |||||
#include <limits.h> | |||||
#include <stdarg.h> | |||||
#include <stdint.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <sys/types.h> | |||||
#include <time.h> | |||||
#ifdef __OpenBSD__ | |||||
#include <unistd.h> | |||||
#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; | |||||
} |
@ -1,42 +0,0 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#ifndef UTIL_H | |||||
#define UTIL_H | |||||
#include <regex.h> | |||||
#include <stddef.h> | |||||
#include <time.h> | |||||
#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 */ |
@ -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; |