Compare commits
26 commits
resource-d
...
master
Author | SHA1 | Date | |
---|---|---|---|
9ce826690d | |||
e8bc00ffd2 | |||
ab42879bc6 | |||
|
0823ba4c3e | ||
|
a36b901d40 | ||
|
db127723c6 | ||
|
a94b15814c | ||
|
9a95d9183c | ||
|
68e4ff3021 | ||
|
c0909c70e4 | ||
|
123f168a3b | ||
670d2ed65c | |||
|
601b56d270 | ||
|
27f8bbfac4 | ||
|
1ccaac023c | ||
|
50c85ec642 | ||
|
68be64e2c1 | ||
|
58d0f44e03 | ||
|
a5163d0813 | ||
|
c1b242e405 | ||
|
6d2fe7f29e | ||
f1fed77826 | |||
ac056a792d | |||
|
ce77dd7962 | ||
|
65600ffe7a | ||
|
3bd49b2456 |
15 changed files with 731 additions and 616 deletions
28
.drone.yml
28
.drone.yml
|
@ -7,31 +7,3 @@ steps:
|
||||||
image: gcc
|
image: gcc
|
||||||
commands:
|
commands:
|
||||||
- make
|
- make
|
||||||
- make minibomb
|
|
||||||
|
|
||||||
- name: run
|
|
||||||
image: debian
|
|
||||||
commands:
|
|
||||||
- cp quark /usr/local/bin
|
|
||||||
- useradd web
|
|
||||||
- mkdir -p web && cd web && echo "hello from quark" > index.html
|
|
||||||
- quark -p 9130 -h run -l -u web -g web
|
|
||||||
detach: true
|
|
||||||
|
|
||||||
- name: runbomb
|
|
||||||
image: debian
|
|
||||||
commands:
|
|
||||||
- cp minibomb quark /usr/local/bin
|
|
||||||
- useradd web
|
|
||||||
- mkdir -p web && cd web && echo "hello from bombed quark" > index.html
|
|
||||||
- su web -c minibomb
|
|
||||||
- quark -p 9131 -h runbomb -l -u web -g web
|
|
||||||
detach: true
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
image: curlimages/curl
|
|
||||||
commands:
|
|
||||||
- sleep 20
|
|
||||||
- curl http://run:9130
|
|
||||||
- curl http://runbomb:9131
|
|
||||||
|
|
||||||
|
|
16
Makefile
16
Makefile
|
@ -4,15 +4,15 @@
|
||||||
|
|
||||||
include config.mk
|
include config.mk
|
||||||
|
|
||||||
COMPONENTS = util sock http resp
|
COMPONENTS = data http sock util
|
||||||
|
|
||||||
all: quark
|
all: quark
|
||||||
|
|
||||||
util.o: util.c util.h config.mk
|
data.o: data.c data.h util.h http.h config.mk
|
||||||
sock.o: sock.c sock.h util.h config.mk
|
http.o: http.c http.h util.h http.h data.h config.h config.mk
|
||||||
http.o: http.c http.h util.h http.h resp.h config.h config.mk
|
|
||||||
resp.o: resp.c resp.h util.h http.h config.mk
|
|
||||||
main.o: main.c util.h sock.h http.h arg.h config.h config.mk
|
main.o: main.c util.h sock.h http.h arg.h config.h config.mk
|
||||||
|
sock.o: sock.c sock.h util.h config.mk
|
||||||
|
util.o: util.c util.h config.mk
|
||||||
|
|
||||||
quark: $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk
|
quark: $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk
|
||||||
$(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS)
|
$(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS)
|
||||||
|
@ -42,9 +42,3 @@ install: all
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f "$(DESTDIR)$(PREFIX)/bin/quark"
|
rm -f "$(DESTDIR)$(PREFIX)/bin/quark"
|
||||||
rm -f "$(DESTDIR)$(MANPREFIX)/man1/quark.1"
|
rm -f "$(DESTDIR)$(MANPREFIX)/man1/quark.1"
|
||||||
|
|
||||||
minibomb: minibomb.c
|
|
||||||
$(CC) -pthread -o $@ $(CPPFLAGS) $(CFLAGS) minibomb.c $(LDFLAGS)
|
|
||||||
|
|
||||||
clean-minibomb:
|
|
||||||
rm -f minibomb
|
|
||||||
|
|
26
README.md
26
README.md
|
@ -1,8 +1,26 @@
|
||||||
|
[![Build Status](https://drone.friedl.net/api/badges/playground/suckless-quark/status.svg)](https://drone.friedl.net/playground/suckless-quark)
|
||||||
|
|
||||||
This is my private tree of [quark](tools.suckless.org/quark/). Upstream can be
|
This is my private tree of [quark](tools.suckless.org/quark/). Upstream can be
|
||||||
found at https://git.suckless.org/quark.
|
found at https://git.suckless.org/quark.
|
||||||
|
|
||||||
Quark is a small http server.
|
Quark is a small http server.
|
||||||
|
|
||||||
|
# Feature Patches
|
||||||
|
|
||||||
|
## Dirl: Customizable directory listing
|
||||||
|
[dirl](https://git.friedl.net/playground/suckless-quark/src/branch/dirlist) lets
|
||||||
|
you serve a fully customizable directory listing.
|
||||||
|
|
||||||
|
You can compile `dirl` from the `dirlist` branch, download a pre-compiled [musl
|
||||||
|
binary](https://dirlist.friedl.net/bin/suckless/quark/quark-dirl) or even pull a
|
||||||
|
pre-made [docker
|
||||||
|
image](https://hub.docker.com/repository/docker/arminfriedl/quark).
|
||||||
|
|
||||||
|
You can find an example deployment of [here](https://dirlist.friedl.net/). It
|
||||||
|
uses the default template just with a custom css. You can define your own
|
||||||
|
templates too for full customization. For details see the dirl
|
||||||
|
[README.md](https://git.friedl.net/playground/suckless-quark/src/branch/dirlist/README.md).
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
|
|
||||||
## fork: Resource temporarily unavailable
|
## fork: Resource temporarily unavailable
|
||||||
|
@ -10,6 +28,14 @@ When running [quark](http://tools.suckless.org/quark/) (#6606994) on my system
|
||||||
with `sudo ./quark -p 9763 -u <user> -g <group>` it dies with `./quark: fork:
|
with `sudo ./quark -p 9763 -u <user> -g <group>` it dies with `./quark: fork:
|
||||||
Resource temporarily unavailable` at `fork()`.
|
Resource temporarily unavailable` at `fork()`.
|
||||||
|
|
||||||
|
Reason being that by default quark sets the RLIMIT_NPROC to 512 processes. When running as a non-exclusive user this limit is easily reached before even starting quark.
|
||||||
|
|
||||||
|
`resource-depletion-fix` contains a small forkbomb (`minibomb.c`) to simulate a user with > 512 processes. Compile it with `make minibomb`. When running the minibomb and quark with the same user quark fails.
|
||||||
|
|
||||||
|
The `resource-depletion-fix` branch contains a fix by setting the RLIMIT_NPROC only if the current system limit is lower than what would be set by quark. You can [download the patch](https://dirlist.friedl.net/suckless/quark/), or compile from the `resource-depletion-fix` branch.
|
||||||
|
|
||||||
|
Note that quark also has a `-n` parameter with which the max number of processes can be set as an alternative to this patch.
|
||||||
|
|
||||||
# Github Users
|
# Github Users
|
||||||
If you are visiting this repository on GitHub, you are on a mirror of
|
If you are visiting this repository on GitHub, you are on a mirror of
|
||||||
https://git.friedl.net/playground/suckless-quark. This mirror is regularily
|
https://git.friedl.net/playground/suckless-quark. This mirror is regularily
|
||||||
|
|
208
data.c
Normal file
208
data.c
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
/* 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 "http.h"
|
||||||
|
#include "data.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
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_send_dirlisting(int fd, const struct response *res)
|
||||||
|
{
|
||||||
|
enum status ret = 0;
|
||||||
|
struct dirent **e;
|
||||||
|
size_t i;
|
||||||
|
int dirlen;
|
||||||
|
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
|
||||||
|
|
||||||
|
/* read directory */
|
||||||
|
if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) {
|
||||||
|
return S_FORBIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* listing header (we use esc because sizeof(esc) >= PATH_MAX) */
|
||||||
|
html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc)));
|
||||||
|
if (dprintf(fd,
|
||||||
|
"<!DOCTYPE html>\n<html>\n\t<head>"
|
||||||
|
"<title>Index of %s</title></head>\n"
|
||||||
|
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
||||||
|
esc) < 0) {
|
||||||
|
ret = S_REQUEST_TIMEOUT;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* listing */
|
||||||
|
for (i = 0; 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 (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
|
||||||
|
esc,
|
||||||
|
(e[i]->d_type == DT_DIR) ? "/" : "",
|
||||||
|
esc,
|
||||||
|
suffix(e[i]->d_type)) < 0) {
|
||||||
|
ret = S_REQUEST_TIMEOUT;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* listing footer */
|
||||||
|
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
|
||||||
|
ret = S_REQUEST_TIMEOUT;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
while (dirlen--) {
|
||||||
|
free(e[dirlen]);
|
||||||
|
}
|
||||||
|
free(e);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum status
|
||||||
|
data_send_error(int fd, const struct response *res)
|
||||||
|
{
|
||||||
|
if (dprintf(fd,
|
||||||
|
"<!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]) < 0) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum status
|
||||||
|
data_send_file(int fd, const struct response *res)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
enum status ret = 0;
|
||||||
|
ssize_t bread, bwritten;
|
||||||
|
size_t remaining;
|
||||||
|
static char buf[BUFSIZ], *p;
|
||||||
|
|
||||||
|
/* open file */
|
||||||
|
if (!(fp = fopen(res->path, "r"))) {
|
||||||
|
ret = S_FORBIDDEN;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* seek to lower bound */
|
||||||
|
if (fseek(fp, res->file.lower, SEEK_SET)) {
|
||||||
|
ret = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write data until upper bound is hit */
|
||||||
|
remaining = res->file.upper - res->file.lower + 1;
|
||||||
|
|
||||||
|
while ((bread = fread(buf, 1, MIN(sizeof(buf),
|
||||||
|
remaining), fp))) {
|
||||||
|
if (bread < 0) {
|
||||||
|
ret = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
remaining -= bread;
|
||||||
|
p = buf;
|
||||||
|
while (bread > 0) {
|
||||||
|
bwritten = write(fd, p, bread);
|
||||||
|
if (bwritten <= 0) {
|
||||||
|
ret = S_REQUEST_TIMEOUT;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
bread -= bwritten;
|
||||||
|
p += bwritten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanup:
|
||||||
|
if (fp) {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
11
data.h
Normal file
11
data.h
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/* See LICENSE file for copyright and license details. */
|
||||||
|
#ifndef DATA_H
|
||||||
|
#define DATA_H
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
|
||||||
|
enum status data_send_dirlisting(int, const struct response *);
|
||||||
|
enum status data_send_error(int, const struct response *);
|
||||||
|
enum status data_send_file(int, const struct response *);
|
||||||
|
|
||||||
|
#endif /* DATA_H */
|
529
http.c
529
http.c
|
@ -17,8 +17,8 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "data.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "resp.h"
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
const char *req_field_str[] = {
|
const char *req_field_str[] = {
|
||||||
|
@ -58,6 +58,12 @@ const char *res_field_str[] = {
|
||||||
[RES_CONTENT_TYPE] = "Content-Type",
|
[RES_CONTENT_TYPE] = "Content-Type",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum status (* const body_fct[])(int, const struct response *) = {
|
||||||
|
[RESTYPE_ERROR] = data_send_error,
|
||||||
|
[RESTYPE_FILE] = data_send_file,
|
||||||
|
[RESTYPE_DIRLISTING] = data_send_dirlisting,
|
||||||
|
};
|
||||||
|
|
||||||
enum status
|
enum status
|
||||||
http_send_header(int fd, const struct response *res)
|
http_send_header(int fd, const struct response *res)
|
||||||
{
|
{
|
||||||
|
@ -89,40 +95,7 @@ http_send_header(int fd, const struct response *res)
|
||||||
return S_REQUEST_TIMEOUT;
|
return S_REQUEST_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res->status;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
enum status
|
|
||||||
http_send_status(int fd, enum status s)
|
|
||||||
{
|
|
||||||
enum status sendstatus;
|
|
||||||
|
|
||||||
struct response res = {
|
|
||||||
.status = s,
|
|
||||||
.field[RES_CONTENT_TYPE] = "text/html; charset=utf-8",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (s == S_METHOD_NOT_ALLOWED) {
|
|
||||||
if (esnprintf(res.field[RES_ALLOW],
|
|
||||||
sizeof(res.field[RES_ALLOW]), "%s",
|
|
||||||
"Allow: GET, HEAD")) {
|
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((sendstatus = http_send_header(fd, &res)) != s) {
|
|
||||||
return sendstatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dprintf(fd,
|
|
||||||
"<!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",
|
|
||||||
s, status_str[s], s, status_str[s]) < 0) {
|
|
||||||
return S_REQUEST_TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -143,44 +116,52 @@ decode(const char src[PATH_MAX], char dest[PATH_MAX])
|
||||||
dest[i] = '\0';
|
dest[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
enum status
|
||||||
http_get_request(int fd, struct request *req)
|
http_recv_header(int fd, char *h, size_t hsiz, size_t *off)
|
||||||
|
{
|
||||||
|
ssize_t r;
|
||||||
|
|
||||||
|
if (h == NULL || off == NULL || *off > hsiz) {
|
||||||
|
return S_INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if ((r = read(fd, h + *off, hsiz - *off)) <= 0) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
*off += r;
|
||||||
|
|
||||||
|
/* check if we are done (header terminated) */
|
||||||
|
if (*off >= 4 && !memcmp(h + *off - 4, "\r\n\r\n", 4)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buffer is full or read over, but header is not terminated */
|
||||||
|
if (r == 0 || *off == hsiz) {
|
||||||
|
return S_REQUEST_TOO_LARGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* header is complete, remove last \r\n and null-terminate */
|
||||||
|
h[*off - 2] = '\0';
|
||||||
|
|
||||||
|
/* set *off to 0 to indicate we are finished */
|
||||||
|
*off = 0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum status
|
||||||
|
http_parse_header(const char *h, struct request *req)
|
||||||
{
|
{
|
||||||
struct in6_addr addr;
|
struct in6_addr addr;
|
||||||
size_t hlen, i, mlen;
|
size_t i, mlen;
|
||||||
ssize_t off;
|
const char *p, *q;
|
||||||
char h[HEADER_MAX], *p, *q;
|
char *m, *n;
|
||||||
|
|
||||||
/* empty all fields */
|
/* empty all fields */
|
||||||
memset(req, 0, sizeof(*req));
|
memset(req, 0, sizeof(*req));
|
||||||
|
|
||||||
/*
|
|
||||||
* receive header
|
|
||||||
*/
|
|
||||||
for (hlen = 0; ;) {
|
|
||||||
if ((off = read(fd, h + hlen, sizeof(h) - hlen)) < 0) {
|
|
||||||
return http_send_status(fd, S_REQUEST_TIMEOUT);
|
|
||||||
} else if (off == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
hlen += off;
|
|
||||||
if (hlen >= 4 && !memcmp(h + hlen - 4, "\r\n\r\n", 4)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (hlen == sizeof(h)) {
|
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* remove terminating empty line */
|
|
||||||
if (hlen < 2) {
|
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
|
||||||
}
|
|
||||||
hlen -= 2;
|
|
||||||
|
|
||||||
/* null-terminate the header */
|
|
||||||
h[hlen] = '\0';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* parse request line
|
* parse request line
|
||||||
*/
|
*/
|
||||||
|
@ -194,12 +175,12 @@ http_get_request(int fd, struct request *req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == NUM_REQ_METHODS) {
|
if (i == NUM_REQ_METHODS) {
|
||||||
return http_send_status(fd, S_METHOD_NOT_ALLOWED);
|
return S_METHOD_NOT_ALLOWED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* a single space must follow the method */
|
/* a single space must follow the method */
|
||||||
if (h[mlen] != ' ') {
|
if (h[mlen] != ' ') {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* basis for next step */
|
/* basis for next step */
|
||||||
|
@ -207,32 +188,32 @@ http_get_request(int fd, struct request *req)
|
||||||
|
|
||||||
/* TARGET */
|
/* TARGET */
|
||||||
if (!(q = strchr(p, ' '))) {
|
if (!(q = strchr(p, ' '))) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
*q = '\0';
|
|
||||||
if (q - p + 1 > PATH_MAX) {
|
if (q - p + 1 > PATH_MAX) {
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
return S_REQUEST_TOO_LARGE;
|
||||||
}
|
}
|
||||||
memcpy(req->target, p, q - p + 1);
|
memcpy(req->uri, p, q - p);
|
||||||
decode(req->target, req->target);
|
req->uri[q - p] = '\0';
|
||||||
|
decode(req->uri, req->uri);
|
||||||
|
|
||||||
/* basis for next step */
|
/* basis for next step */
|
||||||
p = q + 1;
|
p = q + 1;
|
||||||
|
|
||||||
/* HTTP-VERSION */
|
/* HTTP-VERSION */
|
||||||
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
|
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
p += sizeof("HTTP/") - 1;
|
p += sizeof("HTTP/") - 1;
|
||||||
if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
|
if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
|
||||||
strncmp(p, "1.1", sizeof("1.1") - 1)) {
|
strncmp(p, "1.1", sizeof("1.1") - 1)) {
|
||||||
return http_send_status(fd, S_VERSION_NOT_SUPPORTED);
|
return S_VERSION_NOT_SUPPORTED;
|
||||||
}
|
}
|
||||||
p += sizeof("1.*") - 1;
|
p += sizeof("1.*") - 1;
|
||||||
|
|
||||||
/* check terminator */
|
/* check terminator */
|
||||||
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
|
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* basis for next step */
|
/* basis for next step */
|
||||||
|
@ -253,7 +234,7 @@ http_get_request(int fd, struct request *req)
|
||||||
if (i == NUM_REQ_FIELDS) {
|
if (i == NUM_REQ_FIELDS) {
|
||||||
/* unmatched field, skip this line */
|
/* unmatched field, skip this line */
|
||||||
if (!(q = strstr(p, "\r\n"))) {
|
if (!(q = strstr(p, "\r\n"))) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
p = q + (sizeof("\r\n") - 1);
|
p = q + (sizeof("\r\n") - 1);
|
||||||
continue;
|
continue;
|
||||||
|
@ -263,7 +244,7 @@ http_get_request(int fd, struct request *req)
|
||||||
|
|
||||||
/* a single colon must follow the field name */
|
/* a single colon must follow the field name */
|
||||||
if (*p != ':') {
|
if (*p != ':') {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* skip whitespace */
|
/* skip whitespace */
|
||||||
|
@ -272,13 +253,13 @@ http_get_request(int fd, struct request *req)
|
||||||
|
|
||||||
/* extract field content */
|
/* extract field content */
|
||||||
if (!(q = strstr(p, "\r\n"))) {
|
if (!(q = strstr(p, "\r\n"))) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
*q = '\0';
|
|
||||||
if (q - p + 1 > FIELD_MAX) {
|
if (q - p + 1 > FIELD_MAX) {
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
return S_REQUEST_TOO_LARGE;
|
||||||
}
|
}
|
||||||
memcpy(req->field[i], p, q - p + 1);
|
memcpy(req->field[i], p, q - p);
|
||||||
|
req->field[i][q - p] = '\0';
|
||||||
|
|
||||||
/* go to next line */
|
/* go to next line */
|
||||||
p = q + (sizeof("\r\n") - 1);
|
p = q + (sizeof("\r\n") - 1);
|
||||||
|
@ -288,37 +269,37 @@ http_get_request(int fd, struct request *req)
|
||||||
* clean up host
|
* clean up host
|
||||||
*/
|
*/
|
||||||
|
|
||||||
p = strrchr(req->field[REQ_HOST], ':');
|
m = strrchr(req->field[REQ_HOST], ':');
|
||||||
q = strrchr(req->field[REQ_HOST], ']');
|
n = strrchr(req->field[REQ_HOST], ']');
|
||||||
|
|
||||||
/* strip port suffix but don't interfere with IPv6 bracket notation
|
/* strip port suffix but don't interfere with IPv6 bracket notation
|
||||||
* as per RFC 2732 */
|
* as per RFC 2732 */
|
||||||
if (p && (!q || p > q)) {
|
if (m && (!n || m > n)) {
|
||||||
/* port suffix must not be empty */
|
/* port suffix must not be empty */
|
||||||
if (*(p + 1) == '\0') {
|
if (*(m + 1) == '\0') {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
*p = '\0';
|
*m = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* strip the brackets from the IPv6 notation and validate the address */
|
/* strip the brackets from the IPv6 notation and validate the address */
|
||||||
if (q) {
|
if (n) {
|
||||||
/* brackets must be on the outside */
|
/* brackets must be on the outside */
|
||||||
if (req->field[REQ_HOST][0] != '[' || *(q + 1) != '\0') {
|
if (req->field[REQ_HOST][0] != '[' || *(n + 1) != '\0') {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove the right bracket */
|
/* remove the right bracket */
|
||||||
*q = '\0';
|
*n = '\0';
|
||||||
p = req->field[REQ_HOST] + 1;
|
m = req->field[REQ_HOST] + 1;
|
||||||
|
|
||||||
/* validate the contained IPv6 address */
|
/* validate the contained IPv6 address */
|
||||||
if (inet_pton(AF_INET6, p, &addr) != 1) {
|
if (inet_pton(AF_INET6, m, &addr) != 1) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
return S_BAD_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* copy it into the host field */
|
/* copy it into the host field */
|
||||||
memmove(req->field[REQ_HOST], p, q - p + 1);
|
memmove(req->field[REQ_HOST], m, n - m + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -527,172 +508,213 @@ parse_range(const char *str, size_t size, size_t *lower, size_t *upper)
|
||||||
#undef RELPATH
|
#undef RELPATH
|
||||||
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
|
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
|
||||||
|
|
||||||
enum status
|
void
|
||||||
http_send_response(int fd, const struct request *req)
|
http_prepare_response(const struct request *req, struct response *res,
|
||||||
|
const struct server *srv)
|
||||||
{
|
{
|
||||||
enum status returnstatus;
|
enum status s;
|
||||||
struct in6_addr addr;
|
struct in6_addr addr;
|
||||||
struct response res = { 0 };
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
struct tm tm = { 0 };
|
struct tm tm = { 0 };
|
||||||
|
struct vhost *vhost;
|
||||||
size_t len, i;
|
size_t len, i;
|
||||||
size_t lower, upper;
|
|
||||||
int hasport, ipv6host;
|
int hasport, ipv6host;
|
||||||
static char realtarget[PATH_MAX], tmptarget[PATH_MAX];
|
static char realuri[PATH_MAX], tmpuri[PATH_MAX];
|
||||||
char *p, *mime;
|
char *p, *mime;
|
||||||
const char *vhostmatch, *targethost;
|
const char *targethost;
|
||||||
|
|
||||||
/* make a working copy of the target */
|
/* empty all response fields */
|
||||||
memcpy(realtarget, req->target, sizeof(realtarget));
|
memset(res, 0, sizeof(*res));
|
||||||
|
|
||||||
|
/* make a working copy of the URI and normalize it */
|
||||||
|
memcpy(realuri, req->uri, sizeof(realuri));
|
||||||
|
if (normabspath(realuri)) {
|
||||||
|
s = S_BAD_REQUEST;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
/* match vhost */
|
/* match vhost */
|
||||||
vhostmatch = NULL;
|
vhost = NULL;
|
||||||
if (s.vhost) {
|
if (srv->vhost) {
|
||||||
for (i = 0; i < s.vhost_len; i++) {
|
for (i = 0; i < srv->vhost_len; i++) {
|
||||||
/* switch to vhost directory if there is a match */
|
if (!regexec(&(srv->vhost[i].re), req->field[REQ_HOST],
|
||||||
if (!regexec(&s.vhost[i].re, req->field[REQ_HOST], 0,
|
0, NULL, 0)) {
|
||||||
NULL, 0)) {
|
/* we have a matching vhost */
|
||||||
if (chdir(s.vhost[i].dir) < 0) {
|
vhost = &(srv->vhost[i]);
|
||||||
return http_send_status(fd, (errno == EACCES) ?
|
|
||||||
S_FORBIDDEN : S_NOT_FOUND);
|
|
||||||
}
|
|
||||||
vhostmatch = s.vhost[i].chost;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == s.vhost_len) {
|
if (i == srv->vhost_len) {
|
||||||
return http_send_status(fd, S_NOT_FOUND);
|
s = S_NOT_FOUND;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if we have a vhost prefix, prepend it to the target */
|
/* if we have a vhost prefix, prepend it to the URI */
|
||||||
if (s.vhost[i].prefix) {
|
if (vhost->prefix &&
|
||||||
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s",
|
prepend(realuri, LEN(realuri), vhost->prefix)) {
|
||||||
s.vhost[i].prefix, realtarget)) {
|
s = S_REQUEST_TOO_LARGE;
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
goto err;
|
||||||
}
|
|
||||||
memcpy(realtarget, tmptarget, sizeof(realtarget));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* apply target prefix mapping */
|
/* apply URI prefix mapping */
|
||||||
for (i = 0; i < s.map_len; i++) {
|
for (i = 0; i < srv->map_len; i++) {
|
||||||
len = strlen(s.map[i].from);
|
len = strlen(srv->map[i].from);
|
||||||
if (!strncmp(realtarget, s.map[i].from, len)) {
|
if (!strncmp(realuri, srv->map[i].from, len)) {
|
||||||
/* match canonical host if vhosts are enabled and
|
/* match canonical host if vhosts are enabled and
|
||||||
* the mapping specifies a canonical host */
|
* the mapping specifies a canonical host */
|
||||||
if (s.vhost && s.map[i].chost &&
|
if (srv->vhost && srv->map[i].chost &&
|
||||||
strcmp(s.map[i].chost, vhostmatch)) {
|
strcmp(srv->map[i].chost, vhost->chost)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* swap out target prefix */
|
/* swap out URI prefix */
|
||||||
if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s",
|
memmove(realuri, realuri + len, strlen(realuri) + 1);
|
||||||
s.map[i].to, realtarget + len)) {
|
if (prepend(realuri, LEN(realuri), srv->map[i].to)) {
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
memcpy(realtarget, tmptarget, sizeof(realtarget));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* normalize target */
|
/* normalize URI again, in case we introduced dirt */
|
||||||
if (normabspath(realtarget)) {
|
if (normabspath(realuri)) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
s = S_BAD_REQUEST;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* reject hidden target */
|
/* stat the relative path derived from the URI */
|
||||||
if (realtarget[0] == '.' || strstr(realtarget, "/.")) {
|
if (stat(RELPATH(realuri), &st) < 0) {
|
||||||
return http_send_status(fd, S_FORBIDDEN);
|
s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
|
||||||
}
|
goto err;
|
||||||
|
|
||||||
/* stat the target */
|
|
||||||
if (stat(RELPATH(realtarget), &st) < 0) {
|
|
||||||
return http_send_status(fd, (errno == EACCES) ?
|
|
||||||
S_FORBIDDEN : S_NOT_FOUND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
/* add / to target if not present */
|
/* append '/' to URI if not present */
|
||||||
len = strlen(realtarget);
|
len = strlen(realuri);
|
||||||
if (len >= PATH_MAX - 2) {
|
if (len + 1 + 1 > PATH_MAX) {
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
if (len && realtarget[len - 1] != '/') {
|
if (len > 0 && realuri[len - 1] != '/') {
|
||||||
realtarget[len] = '/';
|
realuri[len] = '/';
|
||||||
realtarget[len + 1] = '\0';
|
realuri[len + 1] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* redirect if targets differ, host is non-canonical or we prefixed */
|
/*
|
||||||
if (strcmp(req->target, realtarget) || (s.vhost && vhostmatch &&
|
* reject hidden targets, except if it is a well-known URI
|
||||||
strcmp(req->field[REQ_HOST], vhostmatch))) {
|
* according to RFC 8615
|
||||||
res.status = S_MOVED_PERMANENTLY;
|
*/
|
||||||
|
if (strstr(realuri, "/.") && strncmp(realuri,
|
||||||
|
"/.well-known/", sizeof("/.well-known/") - 1)) {
|
||||||
|
s = S_FORBIDDEN;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
/* encode realtarget */
|
/*
|
||||||
encode(realtarget, tmptarget);
|
* redirect if the original URI and the "real" URI differ or if
|
||||||
|
* the requested host is non-canonical
|
||||||
|
*/
|
||||||
|
if (strcmp(req->uri, realuri) || (srv->vhost && vhost &&
|
||||||
|
strcmp(req->field[REQ_HOST], vhost->chost))) {
|
||||||
|
res->status = S_MOVED_PERMANENTLY;
|
||||||
|
|
||||||
|
/* encode realuri */
|
||||||
|
encode(realuri, tmpuri);
|
||||||
|
|
||||||
/* determine target location */
|
/* determine target location */
|
||||||
if (s.vhost) {
|
if (srv->vhost) {
|
||||||
/* absolute redirection URL */
|
/* absolute redirection URL */
|
||||||
targethost = req->field[REQ_HOST][0] ? vhostmatch ?
|
targethost = req->field[REQ_HOST][0] ? vhost->chost ?
|
||||||
vhostmatch : req->field[REQ_HOST] : s.host ?
|
vhost->chost : req->field[REQ_HOST] :
|
||||||
s.host : "localhost";
|
srv->host ? srv->host : "localhost";
|
||||||
|
|
||||||
/* do we need to add a port to the Location? */
|
/* do we need to add a port to the Location? */
|
||||||
hasport = s.port && strcmp(s.port, "80");
|
hasport = srv->port && strcmp(srv->port, "80");
|
||||||
|
|
||||||
/* RFC 2732 specifies to use brackets for IPv6-addresses
|
/* RFC 2732 specifies to use brackets for IPv6-addresses
|
||||||
* in URLs, so we need to check if our host is one and
|
* in URLs, so we need to check if our host is one and
|
||||||
* honor that later when we fill the "Location"-field */
|
* honor that later when we fill the "Location"-field */
|
||||||
if ((ipv6host = inet_pton(AF_INET6, targethost,
|
if ((ipv6host = inet_pton(AF_INET6, targethost,
|
||||||
&addr)) < 0) {
|
&addr)) < 0) {
|
||||||
return http_send_status(fd,
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
S_INTERNAL_SERVER_ERROR);
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write location to response struct */
|
/* write location to response struct */
|
||||||
if (esnprintf(res.field[RES_LOCATION],
|
if (esnprintf(res->field[RES_LOCATION],
|
||||||
sizeof(res.field[RES_LOCATION]),
|
sizeof(res->field[RES_LOCATION]),
|
||||||
"//%s%s%s%s%s%s",
|
"//%s%s%s%s%s%s",
|
||||||
ipv6host ? "[" : "",
|
ipv6host ? "[" : "",
|
||||||
targethost,
|
targethost,
|
||||||
ipv6host ? "]" : "", hasport ? ":" : "",
|
ipv6host ? "]" : "", hasport ? ":" : "",
|
||||||
hasport ? s.port : "", tmptarget)) {
|
hasport ? srv->port : "", tmpuri)) {
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* write relative redirection URL to response struct */
|
/* write relative redirection URI to response struct */
|
||||||
if (esnprintf(res.field[RES_LOCATION],
|
if (esnprintf(res->field[RES_LOCATION],
|
||||||
sizeof(res.field[RES_LOCATION]),
|
sizeof(res->field[RES_LOCATION]),
|
||||||
tmptarget)) {
|
"%s", tmpuri)) {
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return http_send_header(fd, &res);
|
return;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* the URI is well-formed, we can now write the URI into
|
||||||
|
* the response-URI and corresponding relative path
|
||||||
|
* (optionally including the vhost servedir as a prefix)
|
||||||
|
* into the actual response-path
|
||||||
|
*/
|
||||||
|
if (esnprintf(res->uri, sizeof(res->uri), "%s", req->uri)) {
|
||||||
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
if (esnprintf(res->path, sizeof(res->path), "%s%s",
|
||||||
|
vhost ? vhost->dir : "", RELPATH(req->uri))) {
|
||||||
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
/* append docindex to target */
|
/*
|
||||||
if (esnprintf(realtarget, sizeof(realtarget), "%s%s",
|
* check if the directory index exists by appending it to
|
||||||
req->target, s.docindex)) {
|
* the URI
|
||||||
return http_send_status(fd, S_REQUEST_TOO_LARGE);
|
*/
|
||||||
|
if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s",
|
||||||
|
req->uri, srv->docindex)) {
|
||||||
|
s = S_REQUEST_TOO_LARGE;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stat the docindex, which must be a regular file */
|
/* stat the docindex, which must be a regular file */
|
||||||
if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) {
|
if (stat(RELPATH(tmpuri), &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||||
if (s.listdirs) {
|
if (srv->listdirs) {
|
||||||
/* remove index suffix and serve dir */
|
/* serve directory listing */
|
||||||
realtarget[strlen(realtarget) -
|
res->type = RESTYPE_DIRLISTING;
|
||||||
strlen(s.docindex)] = '\0';
|
res->status = (access(res->path, R_OK)) ?
|
||||||
return resp_dir(fd, RELPATH(realtarget), req);
|
S_FORBIDDEN : S_OK;
|
||||||
|
|
||||||
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
||||||
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
||||||
|
"%s", "text/html; charset=utf-8")) {
|
||||||
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
/* reject */
|
/* reject */
|
||||||
if (!S_ISREG(st.st_mode) || errno == EACCES) {
|
s = (!S_ISREG(st.st_mode) || errno == EACCES) ?
|
||||||
return http_send_status(fd, S_FORBIDDEN);
|
S_FORBIDDEN : S_NOT_FOUND;
|
||||||
} else {
|
goto err;
|
||||||
return http_send_status(fd, S_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -702,39 +724,40 @@ http_send_response(int fd, const struct request *req)
|
||||||
/* parse field */
|
/* parse field */
|
||||||
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
|
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
|
||||||
"%a, %d %b %Y %T GMT", &tm)) {
|
"%a, %d %b %Y %T GMT", &tm)) {
|
||||||
return http_send_status(fd, S_BAD_REQUEST);
|
s = S_BAD_REQUEST;
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* compare with last modification date of the file */
|
/* compare with last modification date of the file */
|
||||||
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
|
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
|
||||||
res.status = S_NOT_MODIFIED;
|
res->status = S_NOT_MODIFIED;
|
||||||
return http_send_header(fd, &res);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* range */
|
/* range */
|
||||||
if ((returnstatus = parse_range(req->field[REQ_RANGE],
|
if ((s = parse_range(req->field[REQ_RANGE], st.st_size,
|
||||||
st.st_size, &lower, &upper))) {
|
&(res->file.lower), &(res->file.upper)))) {
|
||||||
if (returnstatus == S_RANGE_NOT_SATISFIABLE) {
|
if (s == S_RANGE_NOT_SATISFIABLE) {
|
||||||
res.status = S_RANGE_NOT_SATISFIABLE;
|
res->status = S_RANGE_NOT_SATISFIABLE;
|
||||||
|
|
||||||
if (esnprintf(res.field[RES_CONTENT_RANGE],
|
if (esnprintf(res->field[RES_CONTENT_RANGE],
|
||||||
sizeof(res.field[RES_CONTENT_RANGE]),
|
sizeof(res->field[RES_CONTENT_RANGE]),
|
||||||
"bytes */%zu", st.st_size)) {
|
"bytes */%zu", st.st_size)) {
|
||||||
return http_send_status(fd,
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
S_INTERNAL_SERVER_ERROR);
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return http_send_header(fd, &res);
|
return;
|
||||||
} else {
|
} else {
|
||||||
return http_send_status(fd, returnstatus);
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mime */
|
/* mime */
|
||||||
mime = "application/octet-stream";
|
mime = "application/octet-stream";
|
||||||
if ((p = strrchr(realtarget, '.'))) {
|
if ((p = strrchr(realuri, '.'))) {
|
||||||
for (i = 0; i < sizeof(mimes) / sizeof(*mimes); i++) {
|
for (i = 0; i < LEN(mimes); i++) {
|
||||||
if (!strcmp(mimes[i].ext, p + 1)) {
|
if (!strcmp(mimes[i].ext, p + 1)) {
|
||||||
mime = mimes[i].type;
|
mime = mimes[i].type;
|
||||||
break;
|
break;
|
||||||
|
@ -742,5 +765,91 @@ http_send_response(int fd, const struct request *req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp_file(fd, RELPATH(realtarget), req, &st, mime, lower, upper);
|
/* fill response struct */
|
||||||
|
res->type = RESTYPE_FILE;
|
||||||
|
|
||||||
|
/* check if file is readable */
|
||||||
|
res->status = (access(res->path, R_OK)) ? S_FORBIDDEN :
|
||||||
|
(req->field[REQ_RANGE][0] != '\0') ?
|
||||||
|
S_PARTIAL_CONTENT : S_OK;
|
||||||
|
|
||||||
|
if (esnprintf(res->field[RES_ACCEPT_RANGES],
|
||||||
|
sizeof(res->field[RES_ACCEPT_RANGES]),
|
||||||
|
"%s", "bytes")) {
|
||||||
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esnprintf(res->field[RES_CONTENT_LENGTH],
|
||||||
|
sizeof(res->field[RES_CONTENT_LENGTH]),
|
||||||
|
"%zu", res->file.upper - res->file.lower + 1)) {
|
||||||
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
if (req->field[REQ_RANGE][0] != '\0') {
|
||||||
|
if (esnprintf(res->field[RES_CONTENT_RANGE],
|
||||||
|
sizeof(res->field[RES_CONTENT_RANGE]),
|
||||||
|
"bytes %zd-%zd/%zu", res->file.lower,
|
||||||
|
res->file.upper, st.st_size)) {
|
||||||
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
||||||
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
||||||
|
"%s", mime)) {
|
||||||
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
if (timestamp(res->field[RES_LAST_MODIFIED],
|
||||||
|
sizeof(res->field[RES_LAST_MODIFIED]),
|
||||||
|
st.st_mtim.tv_sec)) {
|
||||||
|
s = S_INTERNAL_SERVER_ERROR;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
err:
|
||||||
|
http_prepare_error_response(req, res, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
http_prepare_error_response(const struct request *req,
|
||||||
|
struct response *res, enum status s)
|
||||||
|
{
|
||||||
|
/* used later */
|
||||||
|
(void)req;
|
||||||
|
|
||||||
|
/* empty all response fields */
|
||||||
|
memset(res, 0, sizeof(*res));
|
||||||
|
|
||||||
|
res->type = RESTYPE_ERROR;
|
||||||
|
res->status = s;
|
||||||
|
|
||||||
|
if (esnprintf(res->field[RES_CONTENT_TYPE],
|
||||||
|
sizeof(res->field[RES_CONTENT_TYPE]),
|
||||||
|
"text/html; charset=utf-8")) {
|
||||||
|
res->status = S_INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res->status == S_METHOD_NOT_ALLOWED) {
|
||||||
|
if (esnprintf(res->field[RES_ALLOW],
|
||||||
|
sizeof(res->field[RES_ALLOW]),
|
||||||
|
"Allow: GET, HEAD")) {
|
||||||
|
res->status = S_INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum status
|
||||||
|
http_send_body(int fd, const struct response *res,
|
||||||
|
const struct request *req)
|
||||||
|
{
|
||||||
|
enum status s;
|
||||||
|
|
||||||
|
if (req->method == M_GET && (s = body_fct[res->type](fd, res))) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
49
http.h
49
http.h
|
@ -3,6 +3,9 @@
|
||||||
#define HTTP_H
|
#define HTTP_H
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#define HEADER_MAX 4096
|
#define HEADER_MAX 4096
|
||||||
#define FIELD_MAX 200
|
#define FIELD_MAX 200
|
||||||
|
@ -26,7 +29,7 @@ extern const char *req_method_str[];
|
||||||
|
|
||||||
struct request {
|
struct request {
|
||||||
enum req_method method;
|
enum req_method method;
|
||||||
char target[PATH_MAX];
|
char uri[PATH_MAX];
|
||||||
char field[NUM_REQ_FIELDS][FIELD_MAX];
|
char field[NUM_REQ_FIELDS][FIELD_MAX];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,14 +64,54 @@ enum res_field {
|
||||||
|
|
||||||
extern const char *res_field_str[];
|
extern const char *res_field_str[];
|
||||||
|
|
||||||
|
enum res_type {
|
||||||
|
RESTYPE_ERROR,
|
||||||
|
RESTYPE_FILE,
|
||||||
|
RESTYPE_DIRLISTING,
|
||||||
|
NUM_RES_TYPES,
|
||||||
|
};
|
||||||
|
|
||||||
struct response {
|
struct response {
|
||||||
|
enum res_type type;
|
||||||
enum status status;
|
enum status status;
|
||||||
char field[NUM_RES_FIELDS][FIELD_MAX];
|
char field[NUM_RES_FIELDS][FIELD_MAX];
|
||||||
|
char uri[PATH_MAX];
|
||||||
|
char path[PATH_MAX];
|
||||||
|
struct {
|
||||||
|
size_t lower;
|
||||||
|
size_t upper;
|
||||||
|
} file;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern enum status (* const body_fct[])(int, const struct response *);
|
||||||
|
|
||||||
|
enum conn_state {
|
||||||
|
C_VACANT,
|
||||||
|
C_RECV_HEADER,
|
||||||
|
C_SEND_HEADER,
|
||||||
|
C_SEND_BODY,
|
||||||
|
NUM_CONN_STATES,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct connection {
|
||||||
|
enum conn_state state;
|
||||||
|
int fd;
|
||||||
|
struct sockaddr_storage ia;
|
||||||
|
char header[HEADER_MAX]; /* general req/res-header buffer */
|
||||||
|
size_t off; /* general offset (header/file/dir) */
|
||||||
|
struct request req;
|
||||||
|
struct response res;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum status http_send_header(int, const struct response *);
|
enum status http_send_header(int, const struct response *);
|
||||||
enum status http_send_status(int, enum status);
|
enum status http_send_status(int, enum status);
|
||||||
int http_get_request(int, struct request *);
|
enum status http_recv_header(int, char *, size_t, size_t *);
|
||||||
enum status http_send_response(int, const struct request *);
|
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);
|
||||||
|
enum status http_send_body(int, const struct response *,
|
||||||
|
const struct request *);
|
||||||
|
|
||||||
#endif /* HTTP_H */
|
#endif /* HTTP_H */
|
||||||
|
|
133
main.c
133
main.c
|
@ -16,6 +16,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "sock.h"
|
#include "sock.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
@ -23,41 +24,57 @@
|
||||||
static char *udsname;
|
static char *udsname;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
serve(int infd, const struct sockaddr_storage *in_sa)
|
logmsg(const struct connection *c)
|
||||||
{
|
{
|
||||||
struct request req;
|
char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
|
||||||
time_t t;
|
|
||||||
enum status status;
|
|
||||||
char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
|
|
||||||
char tstmp[21];
|
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%d\t%s\t%s\n", tstmp, inaddr_str, c->res.status,
|
||||||
|
c->req.field[REQ_HOST], c->req.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
serve(struct connection *c, const struct server *srv)
|
||||||
|
{
|
||||||
|
enum status s;
|
||||||
|
|
||||||
/* set connection timeout */
|
/* set connection timeout */
|
||||||
if (sock_set_timeout(infd, 30)) {
|
if (sock_set_timeout(c->fd, 30)) {
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle request */
|
/* handle request */
|
||||||
if (!(status = http_get_request(infd, &req))) {
|
if ((s = http_recv_header(c->fd, c->header, LEN(c->header), &c->off)) ||
|
||||||
status = http_send_response(infd, &req);
|
(s = http_parse_header(c->header, &c->req))) {
|
||||||
|
http_prepare_error_response(&c->req, &c->res, s);
|
||||||
|
} else {
|
||||||
|
http_prepare_response(&c->req, &c->res, srv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write output to log */
|
if ((s = http_send_header(c->fd, &c->res)) ||
|
||||||
t = time(NULL);
|
(s = http_send_body(c->fd, &c->res, &c->req))) {
|
||||||
if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
|
c->res.status = s;
|
||||||
gmtime(&t))) {
|
|
||||||
warn("strftime: Exceeded buffer capacity");
|
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
if (sock_get_inaddr_str(in_sa, inaddr, LEN(inaddr))) {
|
|
||||||
goto cleanup;
|
logmsg(c);
|
||||||
}
|
|
||||||
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, status,
|
|
||||||
req.field[REQ_HOST], req.target);
|
|
||||||
cleanup:
|
cleanup:
|
||||||
/* clean up and finish */
|
/* clean up and finish */
|
||||||
shutdown(infd, SHUT_RD);
|
shutdown(c->fd, SHUT_RD);
|
||||||
shutdown(infd, SHUT_WR);
|
shutdown(c->fd, SHUT_WR);
|
||||||
close(infd);
|
close(c->fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -177,10 +194,11 @@ main(int argc, char *argv[])
|
||||||
struct group *grp = NULL;
|
struct group *grp = NULL;
|
||||||
struct passwd *pwd = NULL;
|
struct passwd *pwd = NULL;
|
||||||
struct rlimit rlim;
|
struct rlimit rlim;
|
||||||
struct sockaddr_storage in_sa;
|
struct server srv = {
|
||||||
|
.docindex = "index.html",
|
||||||
|
};
|
||||||
size_t i;
|
size_t i;
|
||||||
socklen_t in_sa_len;
|
int insock, status = 0;
|
||||||
int insock, status = 0, infd;
|
|
||||||
const char *err;
|
const char *err;
|
||||||
char *tok[4];
|
char *tok[4];
|
||||||
|
|
||||||
|
@ -190,13 +208,6 @@ main(int argc, char *argv[])
|
||||||
char *user = "nobody";
|
char *user = "nobody";
|
||||||
char *group = "nogroup";
|
char *group = "nogroup";
|
||||||
|
|
||||||
s.host = s.port = NULL;
|
|
||||||
s.vhost = NULL;
|
|
||||||
s.map = NULL;
|
|
||||||
s.vhost_len = s.map_len = 0;
|
|
||||||
s.docindex = "index.html";
|
|
||||||
s.listdirs = 0;
|
|
||||||
|
|
||||||
ARGBEGIN {
|
ARGBEGIN {
|
||||||
case 'd':
|
case 'd':
|
||||||
servedir = EARGF(usage());
|
servedir = EARGF(usage());
|
||||||
|
@ -205,28 +216,28 @@ main(int argc, char *argv[])
|
||||||
group = EARGF(usage());
|
group = EARGF(usage());
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
s.host = EARGF(usage());
|
srv.host = EARGF(usage());
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
s.docindex = EARGF(usage());
|
srv.docindex = EARGF(usage());
|
||||||
if (strchr(s.docindex, '/')) {
|
if (strchr(srv.docindex, '/')) {
|
||||||
die("The document index must not contain '/'");
|
die("The document index must not contain '/'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
s.listdirs = 1;
|
srv.listdirs = 1;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
|
if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
|
||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
if (!(s.map = reallocarray(s.map, ++s.map_len,
|
if (!(srv.map = reallocarray(srv.map, ++srv.map_len,
|
||||||
sizeof(struct map)))) {
|
sizeof(struct map)))) {
|
||||||
die("reallocarray:");
|
die("reallocarray:");
|
||||||
}
|
}
|
||||||
s.map[s.map_len - 1].from = tok[0];
|
srv.map[srv.map_len - 1].from = tok[0];
|
||||||
s.map[s.map_len - 1].to = tok[1];
|
srv.map[srv.map_len - 1].to = tok[1];
|
||||||
s.map[s.map_len - 1].chost = tok[2];
|
srv.map[srv.map_len - 1].chost = tok[2];
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
maxnprocs = strtonum(EARGF(usage()), 1, INT_MAX, &err);
|
maxnprocs = strtonum(EARGF(usage()), 1, INT_MAX, &err);
|
||||||
|
@ -235,7 +246,7 @@ main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
s.port = EARGF(usage());
|
srv.port = EARGF(usage());
|
||||||
break;
|
break;
|
||||||
case 'U':
|
case 'U':
|
||||||
udsname = EARGF(usage());
|
udsname = EARGF(usage());
|
||||||
|
@ -248,14 +259,14 @@ main(int argc, char *argv[])
|
||||||
!tok[2]) {
|
!tok[2]) {
|
||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
if (!(s.vhost = reallocarray(s.vhost, ++s.vhost_len,
|
if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len,
|
||||||
sizeof(struct vhost)))) {
|
sizeof(*srv.vhost)))) {
|
||||||
die("reallocarray:");
|
die("reallocarray:");
|
||||||
}
|
}
|
||||||
s.vhost[s.vhost_len - 1].chost = tok[0];
|
srv.vhost[srv.vhost_len - 1].chost = tok[0];
|
||||||
s.vhost[s.vhost_len - 1].regex = tok[1];
|
srv.vhost[srv.vhost_len - 1].regex = tok[1];
|
||||||
s.vhost[s.vhost_len - 1].dir = tok[2];
|
srv.vhost[srv.vhost_len - 1].dir = tok[2];
|
||||||
s.vhost[s.vhost_len - 1].prefix = tok[3];
|
srv.vhost[srv.vhost_len - 1].prefix = tok[3];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
usage();
|
usage();
|
||||||
|
@ -266,7 +277,7 @@ main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
/* can't have both host and UDS but must have one of port or UDS*/
|
/* can't have both host and UDS but must have one of port or UDS*/
|
||||||
if ((s.host && udsname) || !(s.port || udsname)) {
|
if ((srv.host && udsname) || !(srv.port || udsname)) {
|
||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,21 +287,16 @@ main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
|
|
||||||
/* compile and check the supplied vhost regexes */
|
/* compile and check the supplied vhost regexes */
|
||||||
for (i = 0; i < s.vhost_len; i++) {
|
for (i = 0; i < srv.vhost_len; i++) {
|
||||||
if (regcomp(&s.vhost[i].re, s.vhost[i].regex,
|
if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex,
|
||||||
REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
|
REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
|
||||||
die("regcomp '%s': invalid regex",
|
die("regcomp '%s': invalid regex",
|
||||||
s.vhost[i].regex);
|
srv.vhost[i].regex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* raise the process limit */
|
/* raise the process limit */
|
||||||
if (getrlimit(RLIMIT_NPROC, &rlim) < 0) {
|
rlim.rlim_cur = rlim.rlim_max = maxnprocs;
|
||||||
die("getrlimit RLIMIT_NPROC:");
|
|
||||||
}
|
|
||||||
|
|
||||||
rlim.rlim_cur = MAX(rlim.rlim_cur, maxnprocs);
|
|
||||||
rlim.rlim_max = MAX(rlim.rlim_max, maxnprocs);
|
|
||||||
if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
|
if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
|
||||||
die("setrlimit RLIMIT_NPROC:");
|
die("setrlimit RLIMIT_NPROC:");
|
||||||
}
|
}
|
||||||
|
@ -314,7 +320,7 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
/* bind socket */
|
/* bind socket */
|
||||||
insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
|
insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
|
||||||
sock_get_ips(s.host, s.port);
|
sock_get_ips(srv.host, srv.port);
|
||||||
|
|
||||||
switch (fork()) {
|
switch (fork()) {
|
||||||
case -1:
|
case -1:
|
||||||
|
@ -367,9 +373,10 @@ main(int argc, char *argv[])
|
||||||
|
|
||||||
/* accept incoming connections */
|
/* accept incoming connections */
|
||||||
while (1) {
|
while (1) {
|
||||||
in_sa_len = sizeof(in_sa);
|
struct connection c = { 0 };
|
||||||
if ((infd = accept(insock, (struct sockaddr *)&in_sa,
|
|
||||||
&in_sa_len)) < 0) {
|
if ((c.fd = accept(insock, (struct sockaddr *)&c.ia,
|
||||||
|
&(socklen_t){sizeof(c.ia)})) < 0) {
|
||||||
warn("accept:");
|
warn("accept:");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -377,7 +384,7 @@ main(int argc, char *argv[])
|
||||||
/* fork and handle */
|
/* fork and handle */
|
||||||
switch (fork()) {
|
switch (fork()) {
|
||||||
case 0:
|
case 0:
|
||||||
serve(infd, &in_sa);
|
serve(&c, &srv);
|
||||||
exit(0);
|
exit(0);
|
||||||
break;
|
break;
|
||||||
case -1:
|
case -1:
|
||||||
|
@ -385,7 +392,7 @@ main(int argc, char *argv[])
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
default:
|
default:
|
||||||
/* close the connection in the parent */
|
/* close the connection in the parent */
|
||||||
close(infd);
|
close(c.fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
22
minibomb.c
22
minibomb.c
|
@ -1,22 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define FORKS 800
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
for (int i = 0; i < FORKS; i++) {
|
|
||||||
pid_t pid = fork();
|
|
||||||
|
|
||||||
switch (pid) {
|
|
||||||
case -1:
|
|
||||||
perror("Fork failed\n");
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
while (1) sleep(5);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf("Forked %d processes. Letting someone else clean up. Bye.\n", FORKS);
|
|
||||||
}
|
|
18
quark.1
18
quark.1
|
@ -1,4 +1,4 @@
|
||||||
.Dd 2019-02-24
|
.Dd 2020-08-23
|
||||||
.Dt QUARK 1
|
.Dt QUARK 1
|
||||||
.Os suckless.org
|
.Os suckless.org
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -30,6 +30,15 @@
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
.Nm
|
.Nm
|
||||||
is a simple HTTP GET/HEAD-only web server for static content.
|
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
|
.Sh OPTIONS
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl d Ar dir
|
.It Fl d Ar dir
|
||||||
|
@ -46,6 +55,7 @@ The default is "nogroup".
|
||||||
Use
|
Use
|
||||||
.Ar host
|
.Ar host
|
||||||
as the server hostname.
|
as the server hostname.
|
||||||
|
The default is the loopback interface (i.e. localhost).
|
||||||
.It Fl i Ar file
|
.It Fl i Ar file
|
||||||
Set
|
Set
|
||||||
.Ar file
|
.Ar file
|
||||||
|
@ -54,7 +64,7 @@ The default is "index.html".
|
||||||
.It Fl l
|
.It Fl l
|
||||||
Enable directory listing.
|
Enable directory listing.
|
||||||
.It Fl m Ar map
|
.It Fl m Ar map
|
||||||
Add the target prefix mapping rule specified by
|
Add the URI prefix mapping rule specified by
|
||||||
.Ar map ,
|
.Ar map ,
|
||||||
which has the form
|
which has the form
|
||||||
.Qq Pa from to [chost] ,
|
.Qq Pa from to [chost] ,
|
||||||
|
@ -63,7 +73,7 @@ escaped with '\\'.
|
||||||
.Pp
|
.Pp
|
||||||
The prefix
|
The prefix
|
||||||
.Pa from
|
.Pa from
|
||||||
of all matching targets is replaced with
|
of all matching URIs is replaced with
|
||||||
.Pa to ,
|
.Pa to ,
|
||||||
optionally limited to the canonical virtual host
|
optionally limited to the canonical virtual host
|
||||||
.Pa chost .
|
.Pa chost .
|
||||||
|
@ -108,7 +118,7 @@ is redirected to the canonical host
|
||||||
.Pa chost ,
|
.Pa chost ,
|
||||||
if they differ, using the directory
|
if they differ, using the directory
|
||||||
.Pa dir
|
.Pa dir
|
||||||
as the root directory, optionally prefixing the target with
|
as the root directory, optionally prefixing the URI with
|
||||||
.Pa prefix .
|
.Pa prefix .
|
||||||
If any virtual hosts are specified, all requests on non-matching
|
If any virtual hosts are specified, all requests on non-matching
|
||||||
hosts are discarded.
|
hosts are discarded.
|
||||||
|
|
245
resp.c
245
resp.c
|
@ -1,245 +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 "http.h"
|
|
||||||
#include "resp.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
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
|
|
||||||
resp_dir(int fd, const char *name, const struct request *req)
|
|
||||||
{
|
|
||||||
enum status sendstatus;
|
|
||||||
struct dirent **e;
|
|
||||||
struct response res = {
|
|
||||||
.status = S_OK,
|
|
||||||
.field[RES_CONTENT_TYPE] = "text/html; charset=utf-8",
|
|
||||||
};
|
|
||||||
size_t i;
|
|
||||||
int dirlen;
|
|
||||||
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
|
|
||||||
|
|
||||||
/* read directory */
|
|
||||||
if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) {
|
|
||||||
return http_send_status(fd, S_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* send header as late as possible */
|
|
||||||
if ((sendstatus = http_send_header(fd, &res)) != res.status) {
|
|
||||||
res.status = sendstatus;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req->method == M_GET) {
|
|
||||||
/* listing header */
|
|
||||||
html_escape(name, esc, sizeof(esc));
|
|
||||||
if (dprintf(fd,
|
|
||||||
"<!DOCTYPE html>\n<html>\n\t<head>"
|
|
||||||
"<title>Index of %s</title></head>\n"
|
|
||||||
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
|
||||||
esc) < 0) {
|
|
||||||
res.status = S_REQUEST_TIMEOUT;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* listing */
|
|
||||||
for (i = 0; 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 (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
|
|
||||||
esc,
|
|
||||||
(e[i]->d_type == DT_DIR) ? "/" : "",
|
|
||||||
esc,
|
|
||||||
suffix(e[i]->d_type)) < 0) {
|
|
||||||
res.status = S_REQUEST_TIMEOUT;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* listing footer */
|
|
||||||
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
|
|
||||||
res.status = S_REQUEST_TIMEOUT;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
while (dirlen--) {
|
|
||||||
free(e[dirlen]);
|
|
||||||
}
|
|
||||||
free(e);
|
|
||||||
|
|
||||||
return res.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum status
|
|
||||||
resp_file(int fd, const char *name, const struct request *req,
|
|
||||||
const struct stat *st, const char *mime, size_t lower,
|
|
||||||
size_t upper)
|
|
||||||
{
|
|
||||||
FILE *fp;
|
|
||||||
enum status sendstatus;
|
|
||||||
struct response res = {
|
|
||||||
.status = (req->field[REQ_RANGE][0] != '\0') ?
|
|
||||||
S_PARTIAL_CONTENT : S_OK,
|
|
||||||
.field[RES_ACCEPT_RANGES] = "bytes",
|
|
||||||
};
|
|
||||||
ssize_t bread, bwritten;
|
|
||||||
size_t remaining;
|
|
||||||
static char buf[BUFSIZ], *p;
|
|
||||||
|
|
||||||
/* open file */
|
|
||||||
if (!(fp = fopen(name, "r"))) {
|
|
||||||
res.status = http_send_status(fd, S_FORBIDDEN);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* seek to lower bound */
|
|
||||||
if (fseek(fp, lower, SEEK_SET)) {
|
|
||||||
res.status = http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build header */
|
|
||||||
if (esnprintf(res.field[RES_CONTENT_LENGTH],
|
|
||||||
sizeof(res.field[RES_CONTENT_LENGTH]),
|
|
||||||
"%zu", upper - lower + 1)) {
|
|
||||||
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
if (req->field[REQ_RANGE][0] != '\0') {
|
|
||||||
if (esnprintf(res.field[RES_CONTENT_RANGE],
|
|
||||||
sizeof(res.field[RES_CONTENT_RANGE]),
|
|
||||||
"bytes %zd-%zd/%zu", lower, upper,
|
|
||||||
st->st_size)) {
|
|
||||||
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (esnprintf(res.field[RES_CONTENT_TYPE],
|
|
||||||
sizeof(res.field[RES_CONTENT_TYPE]),
|
|
||||||
"%s", mime)) {
|
|
||||||
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
if (timestamp(res.field[RES_LAST_MODIFIED],
|
|
||||||
sizeof(res.field[RES_LAST_MODIFIED]),
|
|
||||||
st->st_mtim.tv_sec)) {
|
|
||||||
return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* send header as late as possible */
|
|
||||||
if ((sendstatus = http_send_header(fd, &res)) != res.status) {
|
|
||||||
res.status = sendstatus;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req->method == M_GET) {
|
|
||||||
/* write data until upper bound is hit */
|
|
||||||
remaining = upper - lower + 1;
|
|
||||||
|
|
||||||
while ((bread = fread(buf, 1, MIN(sizeof(buf),
|
|
||||||
remaining), fp))) {
|
|
||||||
if (bread < 0) {
|
|
||||||
res.status = S_INTERNAL_SERVER_ERROR;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
remaining -= bread;
|
|
||||||
p = buf;
|
|
||||||
while (bread > 0) {
|
|
||||||
bwritten = write(fd, p, bread);
|
|
||||||
if (bwritten <= 0) {
|
|
||||||
res.status = S_REQUEST_TIMEOUT;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
bread -= bwritten;
|
|
||||||
p += bwritten;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleanup:
|
|
||||||
if (fp) {
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status;
|
|
||||||
}
|
|
14
resp.h
14
resp.h
|
@ -1,14 +0,0 @@
|
||||||
/* See LICENSE file for copyright and license details. */
|
|
||||||
#ifndef RESP_H
|
|
||||||
#define RESP_H
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "http.h"
|
|
||||||
|
|
||||||
enum status resp_dir(int, const char *, const struct request *);
|
|
||||||
enum status resp_file(int, const char *, const struct request *,
|
|
||||||
const struct stat *, const char *, size_t, size_t);
|
|
||||||
|
|
||||||
#endif /* RESP_H */
|
|
11
sock.c
11
sock.c
|
@ -63,7 +63,7 @@ void
|
||||||
sock_rem_uds(const char *udsname)
|
sock_rem_uds(const char *udsname)
|
||||||
{
|
{
|
||||||
if (unlink(udsname) < 0) {
|
if (unlink(udsname) < 0) {
|
||||||
die("unlink:");
|
die("unlink '%s':", udsname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ sock_get_uds(const char *udsname, uid_t uid, gid_t gid)
|
||||||
.sun_family = AF_UNIX,
|
.sun_family = AF_UNIX,
|
||||||
};
|
};
|
||||||
size_t udsnamelen;
|
size_t udsnamelen;
|
||||||
int insock, sockmode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
|
int insock, sockmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
|
||||||
|
S_IROTH | S_IWOTH;
|
||||||
|
|
||||||
if ((insock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
if ((insock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
||||||
die("socket:");
|
die("socket:");
|
||||||
|
@ -86,7 +87,7 @@ sock_get_uds(const char *udsname, uid_t uid, gid_t gid)
|
||||||
memcpy(addr.sun_path, udsname, udsnamelen + 1);
|
memcpy(addr.sun_path, udsname, udsnamelen + 1);
|
||||||
|
|
||||||
if (bind(insock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
if (bind(insock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
die("bind %s:", udsname);
|
die("bind '%s':", udsname);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listen(insock, SOMAXCONN) < 0) {
|
if (listen(insock, SOMAXCONN) < 0) {
|
||||||
|
@ -96,12 +97,12 @@ sock_get_uds(const char *udsname, uid_t uid, gid_t gid)
|
||||||
|
|
||||||
if (chmod(udsname, sockmode) < 0) {
|
if (chmod(udsname, sockmode) < 0) {
|
||||||
sock_rem_uds(udsname);
|
sock_rem_uds(udsname);
|
||||||
die("chmod:");
|
die("chmod '%s':", udsname);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chown(udsname, uid, gid) < 0) {
|
if (chown(udsname, uid, gid) < 0) {
|
||||||
sock_rem_uds(udsname);
|
sock_rem_uds(udsname);
|
||||||
die("chown:");
|
die("chown '%s':", udsname);
|
||||||
}
|
}
|
||||||
|
|
||||||
return insock;
|
return insock;
|
||||||
|
|
16
util.c
16
util.c
|
@ -16,7 +16,6 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
char *argv0;
|
char *argv0;
|
||||||
struct server s;
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
verr(const char *fmt, va_list ap)
|
verr(const char *fmt, va_list ap)
|
||||||
|
@ -109,6 +108,21 @@ esnprintf(char *str, size_t size, const char *fmt, ...)
|
||||||
return (ret < 0 || (size_t)ret >= size);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
#define INVALID 1
|
#define INVALID 1
|
||||||
#define TOOSMALL 2
|
#define TOOSMALL 2
|
||||||
#define TOOLARGE 3
|
#define TOOLARGE 3
|
||||||
|
|
5
util.h
5
util.h
|
@ -23,7 +23,7 @@ struct map {
|
||||||
char *to;
|
char *to;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct server {
|
struct server {
|
||||||
char *host;
|
char *host;
|
||||||
char *port;
|
char *port;
|
||||||
char *docindex;
|
char *docindex;
|
||||||
|
@ -32,7 +32,7 @@ extern struct server {
|
||||||
size_t vhost_len;
|
size_t vhost_len;
|
||||||
struct map *map;
|
struct map *map;
|
||||||
size_t map_len;
|
size_t map_len;
|
||||||
} s;
|
};
|
||||||
|
|
||||||
#undef MIN
|
#undef MIN
|
||||||
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
||||||
|
@ -51,6 +51,7 @@ void eunveil(const char *, const char *);
|
||||||
|
|
||||||
int timestamp(char *, size_t, time_t);
|
int timestamp(char *, size_t, time_t);
|
||||||
int esnprintf(char *, size_t, const char *, ...);
|
int esnprintf(char *, size_t, const char *, ...);
|
||||||
|
int prepend(char *, size_t, const char *);
|
||||||
|
|
||||||
void *reallocarray(void *, size_t, size_t);
|
void *reallocarray(void *, size_t, size_t);
|
||||||
long long strtonum(const char *, long long, long long, const char **);
|
long long strtonum(const char *, long long, long long, const char **);
|
||||||
|
|
Loading…
Reference in a new issue