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:
parent
4c14a02754
commit
0e8cac1ee4
8 changed files with 0 additions and 1049 deletions
24
LICENSE
24
LICENSE
|
@ -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.
|
57
Makefile
57
Makefile
|
@ -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
22
README
|
@ -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
63
arg.h
|
@ -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
|
50
config.def.h
50
config.def.h
|
@ -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" }
|
||||
};
|
16
config.mk
16
config.mk
|
@ -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
67
quark.1
|
@ -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
750
quark.c
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue