Prepare repository for rewrite

It was sad to see that quark never got the attention it deserved in my
opinion. However, there were good reasons why that was the case.

The project lost focus by trying to add CGI support, which in all
fairness worked only half of the time.
For the rest of the use cases, a static server to make it dead simple to
publish a directory, it was also pretty bad, given it does not support
partial content. Seeking in a mp3 was impossible and it was very
frustrating.

Long ago we discussed in the team how exciting it would be to test out
new concepts of having a web server that listens on a UNIX-domain
socket, potentially allowing new concepts for realizing virtual hosts
and other things.

It took me half a year to make the decision to rewrite quark, so it is
now time to purge the repo and push the initial commit.
This commit is contained in:
FRIGN 2016-09-02 09:46:01 +02:00
parent 4c14a02754
commit 0e8cac1ee4
8 changed files with 0 additions and 1049 deletions

24
LICENSE
View file

@ -1,24 +0,0 @@
MIT/X Consortium License
© 2009-2011 Anselm R Garbe <anselm@garbe.us>
© 2011 Szabolcs Nagy <nszabolcs@gmail.com>
© 2014 Hiltjo Posthuma <hiltjo@codemadness.org>
© 2014 Laslo Hunhold <dev@frign.de>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View file

@ -1,57 +0,0 @@
# quark - simple httpd get daemon
include config.mk
SRC = quark.c
OBJ = ${SRC:.c=.o}
all: options quark
options:
@echo quark build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
.c.o:
@echo CC $<
@${CC} -c ${CFLAGS} $<
${OBJ}: config.mk config.h
config.h:
@echo creating $@ from config.def.h
@cp config.def.h $@
quark: ${OBJ}
@echo CC -o $@
@${CC} -o $@ ${OBJ} ${LDFLAGS}
clean:
@echo cleaning
@rm -f quark ${OBJ} quark-${VERSION}.tar.gz
dist: clean
@echo creating dist tarball
@mkdir -p quark-${VERSION}
@cp -R LICENSE Makefile README config.mk quark.1 arg.h config.def.h ${SRC} quark-${VERSION}
@tar -cf quark-${VERSION}.tar quark-${VERSION}
@gzip quark-${VERSION}.tar
@rm -rf quark-${VERSION}
install: all
@echo installing executable file to ${DESTDIR}${PREFIX}/bin
@mkdir -p ${DESTDIR}${PREFIX}/bin
@cp -f quark ${DESTDIR}${PREFIX}/bin
@chmod 755 ${DESTDIR}${PREFIX}/bin/quark
@echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
@mkdir -p ${DESTDIR}${MANPREFIX}/man1
@sed "s/VERSION/${VERSION}/g" < quark.1 > ${DESTDIR}${MANPREFIX}/man1/quark.1
uninstall:
@echo removing executable file from ${DESTDIR}${PREFIX}/bin
@rm -f ${DESTDIR}${PREFIX}/bin/quark
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
@rm -f ${DESTDIR}${MANPREFIX}/man1/quark.1
.PHONY: all options clean dist install uninstall

22
README
View file

@ -1,22 +0,0 @@
quark - simple HTTP server
==========================
quark is a small and simple HTTP web server. It serves static
pages on a single host and has basic CGI support.
Installation
------------
Edit config.mk to match your local setup. quark is installed into
/usr/local/bin by default.
Afterwards enter the following command to build and install quark
(if necessary as root):
$ make clean; make
$ sudo make install
Running quark
------------
$ cd <web-root>
$ sudo quark

63
arg.h
View file

@ -1,63 +0,0 @@
/*
* Copy me if you can.
* by 20h
*/
#ifndef ARG_H__
#define ARG_H__
extern char *argv0;
/* use main(int argc, char *argv[]) */
#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
argv[0] && argv[0][1]\
&& argv[0][0] == '-';\
argc--, argv++) {\
char argc_;\
char **argv_;\
int brk_;\
if (argv[0][1] == '-' && argv[0][2] == '\0') {\
argv++;\
argc--;\
break;\
}\
for (brk_ = 0, argv[0]++, argv_ = argv;\
argv[0][0] && !brk_;\
argv[0]++) {\
if (argv_ != argv)\
break;\
argc_ = argv[0][0];\
switch (argc_)
/* Handles obsolete -NUM syntax */
#define ARGNUM case '0':\
case '1':\
case '2':\
case '3':\
case '4':\
case '5':\
case '6':\
case '7':\
case '8':\
case '9'
#define ARGEND }\
}
#define ARGC() argc_
#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
((x), abort(), (char *)0) :\
(brk_ = 1, (argv[0][1] != '\0')?\
(&argv[0][1]) :\
(argc--, argv++, argv[0])))
#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
(char *)0 :\
(brk_ = 1, (argv[0][1] != '\0')?\
(&argv[0][1]) :\
(argc--, argv++, argv[0])))
#endif

View file

@ -1,50 +0,0 @@
/* quark configuration */
static const char *servername = "127.0.0.1";
static const char *serverport = "80";
static const char *chrootdir = ".";
static const char *docroot = ".";
static const char *docindex = "index.html";
static const char *user = "nobody";
static const char *group = "nobody";
static const char *cgi_dir = ".";
static const char *cgi_script = "/werc.rc";
static int cgi_mode = 0;
static int allowdirlist = 0;
static const MimeType servermimes[] = {
{ "html", "text/html; charset=UTF-8" },
{ "htm", "text/html; charset=UTF-8" },
{ "css", "text/css" },
{ "c", "text/plain" },
{ "h", "text/plain" },
{ "go", "text/plain" },
{ "sh", "text/plain" },
{ "log", "text/plain" },
{ "txt", "text/plain" },
{ "md", "text/plain" },
{ "png", "image/png" },
{ "gif", "image/gif" },
{ "jpeg", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "webp", "image/webp" },
{ "xml", "application/xml" },
{ "js", "application/javascript" },
{ "bz2", "application/x-bzip2" },
{ "gz", "application/x-gzip" },
{ "xz", "application/x-xz" },
{ "tgz", "application/x-gtar" },
{ "pdf", "application/x-pdf" },
{ "tar", "application/x-tar" },
{ "zip", "application/zip" },
{ "iso", "application/x-iso9660-image" },
{ "ogx", "application/ogg" },
{ "flac", "audio/flac" },
{ "mp3", "audio/mp3" },
{ "oga", "audio/ogg" },
{ "ogg", "audio/ogg" },
{ "avi", "video/x-msvideo" },
{ "mp4", "video/mp4" },
{ "ogv", "video/ogg" },
{ "webm", "video/webm" }
};

View file

@ -1,16 +0,0 @@
# quark version
VERSION = 0.1
# Customize below to fit your system
# paths
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/share/man
# flags
CPPFLAGS = -DVERSION=\"${VERSION}\" -D_POSIX_C_SOURCE=200809 -D_BSD_SOURCE
CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${CPPFLAGS}
LDFLAGS = # -s
# compiler and linker
CC = cc

67
quark.1
View file

@ -1,67 +0,0 @@
.TH QUARK 1 quark\-VERSION
.SH NAME
quark \- simple httpd
.SH SYNOPSIS
.B quark
.RB [ \-c ]
.RB [ \-C
.IR chrootdir ]
.RB [ \-d
.IR cgidir ]
.RB [ \-e
.IR cgiscript ]
.RB [ \-g
.IR group ]
.RB [ \-i
.IR index ]
.RB [ \-l ]
.RB [ \-p
.IR port ]
.RB [ \-r
.IR docroot ]
.RB [ \-s
.IR server ]
.RB [ \-u
.IR user ]
.RB [ \-v ]
.SH DESCRIPTION
Quark is a simple httpd.
.SH OPTIONS
.TP
.B \-c
enable CGI-mode, disabled by default.
.TP
.B \-C " chrootdir"
chroot into chrootdir, by default ".".
.TP
.B \-d " cgidir"
change directory to cgidir for CGI-mode, by default ".".
.TP
.B \-e " cgiscript"
CGI-script to execute for each request in CGI-mode, by default "/werc.rc".
.TP
.B \-g " group"
change process group to group, by default "nobody".
.TP
.B \-i " indexfile"
index file, by default "index.html".
.TP
.B \-l
enable directory listing, disabled by default.
.TP
.B \-p
listen on port, by default "80".
.TP
.B \-r " docroot"
change directory to docroot for static files, by default ".".
.TP
.B \-s " server"
listen on server, by default "127.0.0.1".
.TP
.B \-u " user"
change process owner to user, by default "nobody".
.TP
.B \-v
show version information.
.SH BUGS
Please report them!

750
quark.c
View file

@ -1,750 +0,0 @@
/* See LICENSE file for license details. */
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "arg.h"
char *argv0;
#define LENGTH(x) (sizeof x / sizeof x[0])
#define MAXBUFLEN 1024
#define NPROCS 512
#undef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define HttpOk "200 OK"
#define HttpMoved "301 Moved Permanently"
#define HttpNotModified "304 Not Modified"
#define HttpForbidden "403 Forbidden"
#define HttpNotFound "404 Not Found"
#define texthtml "text/html"
enum {
GET = 4,
HEAD = 5,
};
typedef struct {
const char *extension;
const char *mimetype;
} MimeType;
typedef struct {
int type;
int fd;
} Request;
enum {
HEADER,
CONTENTLEN,
LOCATION,
CONTENTTYPE,
MODIFIED
};
static const char *resentry[] = {
[HEADER] = "HTTP/1.1 %s\r\n"
"Connection: close\r\n"
"Date: %s\r\n"
"Server: quark-"VERSION"\r\n",
[CONTENTLEN] = "Content-Length: %lu\r\n",
[LOCATION] = "Location: %s%s\r\n",
[CONTENTTYPE] = "Content-Type: %s\r\n",
[MODIFIED] = "Last-Modified: %s\r\n"
};
static char *tstamp(time_t t);
static int writedata(const char *buf, size_t buflen);
static int writetext(const char *buf);
static void atomiclog(int fd, const char *errstr, va_list ap);
static void logmsg(const char *errstr, ...);
static void logerrmsg(const char *errstr, ...);
static void die(const char *errstr, ...);
static int putresentry(int type, ...);
static void responsefiledata(int fd, off_t size);
static void responsefile(void);
static void responsedirdata(struct dirent **e, int len);
static void responsedir(void);
static void responsecgi(void);
static void response(void);
static int getreqentry(char *name, char *target, size_t targetlen, char *breakchars);
static int request(void);
static void serve(int fd);
static void sighandler(int sig);
#include "config.h"
static char location[256];
static int running = 1;
static int status;
static char host[NI_MAXHOST];
static char* reqbuf = NULL;
static char* reqpath = NULL;
static char resbuf[MAXBUFLEN];
static char reqhost[256];
static char reqmod[256];
static int listenfd = -1;
static Request req;
char *
tstamp(time_t t)
{
static char res[30];
if (!t)
t = time(NULL);
strftime(res, sizeof res, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
return res;
}
int
writedata(const char *buf, size_t buf_len)
{
ssize_t r, offset;
for (offset = 0; offset < buf_len; offset += r) {
if ((r = write(req.fd, buf + offset, buf_len - offset)) == -1) {
logerrmsg("client %s closed connection\n", host);
return -1;
}
}
return 0;
}
int
writetext(const char *buf)
{
return writedata(buf, strlen(buf));
}
void
atomiclog(int fd, const char *errstr, va_list ap)
{
static char buf[512];
size_t n;
/* assemble the message in buf and write it in one pass
to avoid interleaved concurrent writes on a shared fd. */
n = snprintf(buf, sizeof buf, "%s\t", tstamp(0));
n += vsnprintf(buf + n, sizeof buf - n, errstr, ap);
if (n >= sizeof buf)
n = sizeof buf - 1;
write(fd, buf, n);
}
void
logmsg(const char *errstr, ...)
{
va_list ap;
va_start(ap, errstr);
atomiclog(STDOUT_FILENO, errstr, ap);
va_end(ap);
}
void
logerrmsg(const char *errstr, ...)
{
va_list ap;
va_start(ap, errstr);
atomiclog(STDERR_FILENO, errstr, ap);
va_end(ap);
}
void
die(const char *errstr, ...)
{
va_list ap;
va_start(ap, errstr);
atomiclog(STDERR_FILENO, errstr, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
int
putresentry(int type, ...)
{
va_list ap;
va_start(ap, type);
if (vsnprintf(resbuf, MAXBUFLEN, resentry[type], ap) >= MAXBUFLEN) {
logerrmsg("vsnprintf failed, buffer size exceeded");
return -1;
}
va_end(ap);
return writetext(resbuf);
}
void
responsefiledata(int fd, off_t size)
{
char buf[BUFSIZ];
ssize_t n, m = 0, size_in;
for (; (n = read(fd, buf, MIN(size, sizeof buf))) > 0; size -= n)
for (size_in = n; (m = write(req.fd, buf, size_in)) > 0; size_in -= m);
if (m == -1 && errno != EPIPE)
logerrmsg("error writing to client %s: %s\n", host, strerror(errno));
if (n == -1)
logerrmsg("error reading from file: %s\n", strerror(errno));
}
void
responsefile(void)
{
const char *mimetype = "application/octet-stream";
char mod[30], *p;
int r, ffd;
struct stat st;
if ((r = stat(reqpath, &st)) == -1 || (ffd = open(reqpath, O_RDONLY)) == -1) {
/* file not found */
if (putresentry(HEADER, HttpNotFound, tstamp(0))
|| putresentry(CONTENTTYPE, texthtml))
return;
status = 404;
if (req.type == GET)
writetext("\r\n<html><body>"HttpNotFound"</body></html>\r\n");
} else {
snprintf(mod, sizeof(mod), "%s", tstamp(st.st_mtim.tv_sec));
/* check if modified */
if (!strcmp(reqmod, mod)
&& !putresentry(HEADER, HttpNotModified, tstamp(0))) {
/* not modified, we're done here*/
status = 304;
} else {
/* determine mime-type */
if ((p = strrchr(reqbuf, '.'))) {
p++;
for (r = 0; r < LENGTH(servermimes); r++)
if (!strcmp(servermimes[r].extension, p)) {
mimetype = servermimes[r].mimetype;
break;
}
}
/* serve file */
if (putresentry(HEADER, HttpOk, tstamp(0))
|| putresentry(MODIFIED, mod)
|| putresentry(CONTENTLEN, st.st_size)
|| putresentry(CONTENTTYPE, mimetype))
return;
status = 200;
if (req.type == GET && !writetext("\r\n"))
responsefiledata(ffd, st.st_size);
}
close(ffd);
}
}
void
responsedirdata(struct dirent **e, int len)
{
int n;
if (putresentry(HEADER, HttpOk, tstamp(0))
|| putresentry(CONTENTTYPE, texthtml))
return;
status = 200;
if (req.type == GET) {
if (writetext("\r\n<html><body><a href=\"..\">..</a><br/>\r\n"))
return;
for (n = 0; n < len; n++) {
if (e[n]->d_name[0] == '.') /* ignore hidden files, ., .. */
continue;
if (snprintf(resbuf, MAXBUFLEN, "<a href=\"%s%s\">%s</a><br/>\r\n",
reqbuf, e[n]->d_name, e[n]->d_name) >= MAXBUFLEN)
{
logerrmsg("snprintf failed, buffer sizeof exceeded");
return;
}
if (writetext(resbuf))
return;
}
writetext("</body></html>\r\n");
}
}
void
responsedir(void)
{
size_t len = strlen(reqbuf);
struct dirent **namelist = NULL;
int n;
if (len > 0 && (reqbuf[len - 1] != '/') && (len + 1 < MAXBUFLEN)) {
/* add directory terminator if necessary */
reqbuf[len] = '/';
reqbuf[len + 1] = 0;
if (putresentry(HEADER, HttpMoved, tstamp(0))
|| putresentry(LOCATION, location, reqbuf)
|| putresentry(CONTENTTYPE, texthtml))
return;
status = 301;
reqbuf[len] = 0;
if (req.type == GET)
writetext("\r\n<html><body>"HttpMoved"</a></body></html>\r\n");
return;
}
if (len + strlen(docindex) + 1 < MAXBUFLEN)
memmove(reqbuf + len, docindex, strlen(docindex) + 1);
if (access(reqpath, R_OK) == -1) { /* directory mode */
reqbuf[len] = 0; /* cut off docindex again */
if (!allowdirlist) {
if (putresentry(HEADER, HttpForbidden, tstamp(0))
|| putresentry(CONTENTTYPE, texthtml))
return;
status = 403;
if (req.type == GET)
writetext("\r\n<html><body>"HttpForbidden"</body></html>\r\n");
return;
}
if ((n = scandir(reqpath, &namelist, NULL, alphasort)) >= 0) {
responsedirdata(namelist, n);
free(namelist);
} else {
logerrmsg("client %s requests %s but scandir failed: %s\n",
host, reqpath, strerror(errno));
}
} else {
responsefile(); /* docindex */
}
}
void
responsecgi(void)
{
FILE *cgi;
size_t r, linesiz = 0;
char *q, *line = NULL, *statusline = HttpOk;
ssize_t linelen;
if (req.type == GET)
setenv("REQUEST_METHOD", "GET", 1);
else if (req.type == HEAD)
setenv("REQUEST_METHOD", "HEAD", 1);
else
return;
if (*reqhost)
setenv("SERVER_NAME", reqhost, 1);
if ((q = strchr(reqbuf, '?'))) {
setenv("QUERY_STRING", q + 1, 1);
*q = '\0';
setenv("PATH_INFO", reqbuf, 1);
*q = '?';
} else {
setenv("QUERY_STRING", "", 1);
setenv("PATH_INFO", reqbuf, 1);
}
setenv("SERVER_PORT", serverport, 1);
setenv("SERVER_SOFTWARE", "quark-"VERSION, 1);
setenv("SCRIPT_NAME", cgi_script, 1);
setenv("REMOTE_ADDR", host, 1);
setenv("REQUEST_URI", reqbuf, 1);
logmsg("CGI SERVER_NAME=%s SCRIPT_NAME=%s REQUEST_URI=%s\n",
reqhost, cgi_script, reqbuf);
if (chdir(cgi_dir) == -1)
logerrmsg("error\tchdir to cgi directory %s failed: %s\n",
cgi_dir, strerror(errno));
if ((cgi = popen(cgi_script, "r"))) {
status = 200;
if ((linelen = getline(&line, &linesiz, cgi)) > 0) {
if (strncmp(line, "Status:", strlen("Status:")) == 0) {
statusline = line + strlen("Status:") + 1;
errno = 0;
status = strtol(statusline, NULL, 10);
if (errno)
status = 200;
if (putresentry(HEADER, statusline, tstamp(0)))
return;
writedata(line, linelen);
} else {
if (putresentry(HEADER, statusline, tstamp(0)))
return;
}
}
while ((r = fread(resbuf, 1, MAXBUFLEN, cgi)) > 0) {
if (writedata(resbuf, r)) {
pclose(cgi);
return;
}
}
free(line);
pclose(cgi);
} else {
logerrmsg("error\t%s requests %s, but cannot run cgi script %s: %s\n",
host, reqbuf, cgi_script, strerror(errno));
if (putresentry(HEADER, HttpNotFound, tstamp(0))
|| putresentry(CONTENTTYPE, texthtml))
return;
status = 404;
if (req.type == GET)
writetext("\r\n<html><body>"HttpNotFound"</body></html>\r\n");
}
}
void
response(void)
{
char *p;
struct stat st;
int r;
for (p = reqbuf; *p; p++) {
if (*p == '\\' || (*p == '/' && *(p + 1) == '.')) {
/* don't serve bogus or hidden files */
if (putresentry(HEADER, HttpForbidden, tstamp(0))
|| putresentry(CONTENTTYPE, texthtml))
return;
status = 403;
if (req.type == GET)
writetext("\r\n<html><body>"HttpForbidden"</body></html>\r\n");
return;
}
}
r = stat(reqpath, &st);
if (cgi_mode) {
if (r != -1 && !S_ISDIR(st.st_mode))
responsefile();
else
responsecgi();
} else {
if (r != -1 && S_ISDIR(st.st_mode))
responsedir();
else
responsefile();
}
}
int
getreqentry(char *name, char *target, size_t targetlen, char *breakchars)
{
char *p, *res;
if ((res = strstr(reqbuf, name))) {
for (res = res + strlen(name); *res && (*res == ' ' || *res == '\t'); ++res);
if (!*res)
return 1;
for (p = res; *p && !strchr(breakchars, *p); ++p);
if (!*p)
return 1;
if ((size_t)(p - res) >= targetlen)
return 1;
memcpy(target, res, p - res);
target[p - res] = 0;
return 0;
}
return -1;
}
int
request(void)
{
char *p, *res;
ssize_t r;
size_t offset = 0;
/* read request into reqbuf (MAXBUFLEN byte of reqbuf is emergency 0 terminator) */
for (; (r = read(req.fd, reqbuf + offset, MAXBUFLEN - offset - 1)) > 0 && offset < MAXBUFLEN
&& !strstr(reqbuf, "\r\n\r\n") && !strstr(reqbuf, "\n\n"); )
{
offset += r;
reqbuf[offset] = 0;
}
if (r == -1) {
logerrmsg("error\tread: %s\n", strerror(errno));
return -1;
}
/* extract host and mod */
if (getreqentry("Host:", reqhost, LENGTH(reqhost), " \t\r\n") != 0)
goto invalid_request;
if (getreqentry("If-Modified-Since:", reqmod, LENGTH(reqmod), "\r\n") == 1)
goto invalid_request;
/* extract method */
for (p = reqbuf; *p && *p != '\r' && *p != '\n'; p++);
if (*p == '\r' || *p == '\n') {
*p = 0;
/* check command */
if (!strncmp(reqbuf, "GET ", 4) && reqbuf[4] == '/')
req.type = GET;
else if (!strncmp(reqbuf, "HEAD ", 5) && reqbuf[5] == '/')
req.type = HEAD;
else
goto invalid_request;
} else {
goto invalid_request;
}
/* determine path */
for (res = reqbuf + req.type; *res && *(res + 1) == '/'; res++); /* strip '/' */
if (!*res)
goto invalid_request;
for (p = res; *p && *p != ' ' && *p != '\t'; p++);
if (!*p)
goto invalid_request;
*p = 0;
memmove(reqbuf, res, (p - res) + 1);
return 0;
invalid_request:
return -1;
}
void
serve(int fd)
{
int result;
struct timeval tv;
socklen_t salen;
struct sockaddr sa;
while (running) {
salen = sizeof sa;
if ((req.fd = accept(fd, &sa, &salen)) == -1) {
logerrmsg("info\tcannot accept: %s\n", strerror(errno));
continue;
}
result = fork();
if (result == 0) {
close(fd);
/* get host */
host[0] = 0;
switch(sa.sa_family) {
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in *)&sa)->sin_addr),
host, sizeof host);
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)&sa)->sin6_addr),
host, sizeof host);
break;
}
/* If we haven't received any data within this period, close the
* socket to avoid spamming the process table */
tv.tv_sec = 30;
tv.tv_usec = 0;
if (setsockopt(req.fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
logerrmsg("error\tsetsockopt SO_RCVTIMEO failed: %s\n",
strerror(errno));
result = request();
shutdown(req.fd, SHUT_RD);
status = -1;
if (result == 0)
response();
logmsg("%d\t%s\t%s\n", status, host, reqbuf);
shutdown(req.fd, SHUT_WR);
close(req.fd);
exit(EXIT_SUCCESS);
} else if (result == -1) {
logerrmsg("error\tfork failed: %s\n", strerror(errno));
}
close(req.fd);
}
logmsg("info\tshutting down\n");
}
void
sighandler(int sig)
{
if (sig == SIGCHLD) {
while (0 < waitpid(-1, NULL, WNOHANG));
} else {
logerrmsg("info\tsignal %s, closing down\n", strsignal(sig));
close(listenfd);
running = 0;
}
}
void
usage(void)
{
fprintf(stderr, "usage: quark [-c] [-C chrootdir] [-d cgidir] "
"[-e cgiscript] [-g group] [-i index] [-l] [-p port] "
"[-r docroot] [-s server] [-u user] [-v]\n");
exit(EXIT_FAILURE);
}
int
main(int argc, char *argv[])
{
struct addrinfo hints, *ai = NULL;
struct passwd *upwd = NULL;
struct group *gpwd = NULL;
struct rlimit rlim;
int i, docrootlen, optval;
ARGBEGIN {
case 'c':
cgi_mode = 1;
break;
case 'C':
chrootdir = EARGF(usage());
break;
case 'd':
cgi_dir = EARGF(usage());
break;
case 'e':
cgi_script = EARGF(usage());
break;
case 'u':
user = EARGF(usage());
break;
case 'g':
group = EARGF(usage());
break;
case 'i':
docindex = EARGF(usage());
break;
case 'l':
allowdirlist = 1;
break;
case 'r':
docroot = EARGF(usage());
break;
case 'p':
serverport = EARGF(usage());
break;
case 's':
servername = EARGF(usage());
break;
case 'v':
die("quark-"VERSION"\n");
default:
usage();
} ARGEND;
/* sanity checks */
if (user && *user && !(upwd = getpwnam(user)))
die("error\tinvalid user %s\n", user);
if (group && *group && !(gpwd = getgrnam(group)))
die("error\tinvalid group %s\n", group);
docrootlen = strlen(docroot);
reqpath = malloc(docrootlen + MAXBUFLEN);
if (reqpath == NULL) {
logerrmsg("error\tcannot allocate memory\n");
goto err;
}
memcpy(reqpath, docroot, docrootlen + 1);
reqbuf = reqpath + docrootlen;
signal(SIGCHLD, sighandler);
signal(SIGHUP, sighandler);
signal(SIGINT, sighandler);
signal(SIGQUIT, sighandler);
signal(SIGABRT, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, SIG_IGN);
/* init */
setbuf(stdout, NULL); /* unbuffered stdout */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((i = getaddrinfo(servername, serverport, &hints, &ai))) {
logerrmsg("error\tgetaddrinfo: %s\n", gai_strerror(i));
goto err;
}
if ((listenfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
logerrmsg("error\tsocket: %s\n", strerror(errno));
goto err;
}
optval = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
logerrmsg("error\tsetsockopt SO_REUSEADDR failed: %s\n",
strerror(errno));
if (bind(listenfd, ai->ai_addr, ai->ai_addrlen) == -1) {
logerrmsg("error\tbind: %s\n", strerror(errno));
goto err;
}
if (listen(listenfd, SOMAXCONN) == -1) {
logerrmsg("error\tlisten: %s\n", strerror(errno));
goto err;
}
if (!strcmp(serverport, "80"))
i = snprintf(location, sizeof location, "http://%s", servername);
else
i = snprintf(location, sizeof location, "http://%s:%s", servername, serverport);
if (i >= sizeof location) {
logerrmsg("error\tlocation too long\n");
goto err;
}
rlim.rlim_cur = NPROCS;
rlim.rlim_max = NPROCS;
if (setrlimit(RLIMIT_NPROC, &rlim) == -1) {
logerrmsg("error\tsetrlimit RLIMIT_NPROC: %s\n", strerror(errno));
goto err;
}
if (chdir(chrootdir) == -1) {
logerrmsg("error\tchdir %s: %s\n", chrootdir, strerror(errno));
goto err;
}
if (chroot(".") == -1) {
logerrmsg("error\tchroot .: %s\n", strerror(errno));
goto err;
}
if (gpwd && setgid(gpwd->gr_gid) == -1) {
logerrmsg("error\tcannot set group id\n");
goto err;
}
if (upwd && setuid(upwd->pw_uid) == -1) {
logerrmsg("error\tcannot set user id\n");
goto err;
}
if (getuid() == 0) {
logerrmsg("error\twon't run with root permissions, choose another user\n");
goto err;
}
if (getgid() == 0) {
logerrmsg("error\twon't run with root permissions, choose another group\n");
goto err;
}
logmsg("ready\t%s:%s\t%s\n", servername, serverport, chrootdir);
serve(listenfd); /* main loop */
close(listenfd);
free(reqpath);
freeaddrinfo(ai);
return EXIT_SUCCESS;
err:
if (listenfd != -1)
close(listenfd);
free(reqpath);
if (ai)
freeaddrinfo(ai);
return EXIT_FAILURE;
}