@ -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; |