initial commit

This commit is contained in:
Anselm R Garbe 2009-08-15 19:56:11 +01:00
commit ce31aca1aa
6 changed files with 568 additions and 0 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT/X Consortium License
© 2009 Anselm R Garbe <anselm@garbe.us>
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.

52
Makefile Normal file
View file

@ -0,0 +1,52 @@
# 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
quark: ${OBJ}
@echo CC -o $@
@${CC} -o $@ ${OBJ} ${LDFLAGS}
@strip $@
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 ${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
uninstall:
@echo removing executable file from ${DESTDIR}${PREFIX}/bin
@rm -f ${DESTDIR}${PREFIX}/bin/quark
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
.PHONY: all options clean dist install uninstall

19
README Normal file
View file

@ -0,0 +1,19 @@
quark - simple http get server
==============================
quark is an extremly small and simple http get-only web server.
Installation
------------
Edit config.mk to match your local setup. quark is installed into
/usr/local/sbin by default.
Afterwards enter the following command to build and install quark
(if necessary as root):
$ make clean install
Running quark
------------
Simply invoke the quark <web-root> as superuser.

23
config.h Normal file
View file

@ -0,0 +1,23 @@
/* quark configuration */
static const char servername[] = "127.0.0.1";
static const char serverport[] = "80";
static const char docroot[] = ".";
static const char docindex[] = "index.html";
static const char user[] = "www-data";
static const char group[] = "www-data";
static const MimeType servermimes[] = {
{ "html", "text/html; charset=UTF-8" },
{ "htm", "text/html; charset=UTF-8" },
{ "css", "text/css" },
{ "txt", "text/plain" },
{ "text", "text/plain" },
{ "png", "image/png" },
{ "gif", "image/gif" },
{ "jpg", "image/jpg" },
{ "iso", "application/x-iso9660-image" },
{ "gz", "application/x-gtar" },
{ "pdf", "application/x-pdf" },
{ "tar", "application/tar" },
};

21
config.mk Normal file
View file

@ -0,0 +1,21 @@
# quark version
VERSION = 0.1
# Customize below to fit your system
# paths
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/share/man
# includes and libs
INCS = -I. -I/usr/include
LIBS = -L/usr/lib -lc
# flags
CPPFLAGS = -DVERSION=\"${VERSION}\" -D_GNU_SOURCE
CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
LDFLAGS = ${LIBS}
#LDFLAGS = -s ${LIBS}
# compiler and linker
CC = cc

432
quark.c Normal file
View file

@ -0,0 +1,432 @@
/* See LICENSE file for license details. */
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define LENGTH(x) (sizeof x / sizeof x[0])
#define MAXREQLEN 256
typedef struct {
const char *extension;
const char *mimetype;
} MimeType;
static const char HttpOk[] = "200 OK";
static const char HttpMoved[] = "302 Moved Permanently";
static const char HttpUnauthorized[] = "401 Unauthorized";
static const char HttpNotFound[] = "404 Not Found";
static const char texthtml[] = "text/html";
static ssize_t writedata(const char *buf);
static void die(const char *errstr, ...);
static void response(void);
static void responsedir(void);
static void responsedirdata(DIR *d);
static void responsefile(void);
static void responsefiledata(int fd, off_t size);
static int request(void);
static void serve(int fd);
static void sighandler(int sig);
static char *tstamp(void);
#include "config.h"
static char location[256];
static int running = 1;
static char name[128];
static char reqbuf[MAXREQLEN];
static char respbuf[1024];
static int fd, cfd;
ssize_t
writedata(const char *buf) {
ssize_t r, offset = 0;
size_t len = strlen(buf);
while(offset < len) {
if((r = write(cfd, buf + offset, len - offset)) == -1) {
fprintf(stderr, "%s: client %s closed connection\n", tstamp(), name);
return -1;
}
offset += r;
}
return offset;
}
void
logmsg(const char *errstr, ...) {
va_list ap;
fprintf(stdout, "%s: ", tstamp());
va_start(ap, errstr);
vfprintf(stdout, errstr, ap);
va_end(ap);
}
void
logerrmsg(const char *errstr, ...) {
va_list ap;
fprintf(stderr, "%s: ", tstamp());
va_start(ap, errstr);
vfprintf(stderr, errstr, ap);
va_end(ap);
}
void
die(const char *errstr, ...) {
va_list ap;
fprintf(stderr, "%s: ", tstamp());
va_start(ap, errstr);
vfprintf(stderr, errstr, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
int
responsehdr(const char *status) {
if(snprintf(respbuf, sizeof respbuf,
"HTTP/1.1 %s\r\n"
"Connection: close\r\n"
"Date: %s\r\n"
"Server: quark-"VERSION"\r\n",
status, tstamp()) >= sizeof respbuf)
{
logerrmsg("snprintf failed, buffer size exceeded");
return -1;
}
return writedata(respbuf);
}
int
responsecontentlen(off_t size) {
if(snprintf(respbuf, sizeof respbuf,
"Content-Length: %lu\r\n",
size) >= sizeof respbuf)
{
logerrmsg("snprintf failed, buffer sizeof exceeded");
return -1;
}
return writedata(respbuf);
}
int
responselocation(const char *location, const char *pathinfo) {
if(snprintf(respbuf, sizeof respbuf,
"Location: %s%s\r\n",
location, pathinfo) >= sizeof respbuf)
{
logerrmsg("snprintf failed, buffer sizeof exceeded");
return -1;
}
return writedata(respbuf);
}
int
responsecontenttype(const char *mimetype) {
if(snprintf(respbuf, sizeof respbuf,
"Content-Type: %s\r\n",
mimetype) >= sizeof respbuf)
{
logerrmsg("snprintf failed, buffer sizeof exceeded");
return -1;
}
return writedata(respbuf);
}
void
responsefiledata(int fd, off_t size) {
off_t offset = 0;
while(offset < size)
if(sendfile(cfd, fd, &offset, size - offset) == -1) {
fprintf(stderr, "%s: sendfile failed on client %s: %s\n", tstamp(), name, strerror(errno));
return;
}
}
void
responsefile(void) {
const char *mimetype = "unknown";
char *p;
int i, ffd;
struct stat st;
stat(reqbuf, &st);
if((ffd = open(reqbuf, O_RDONLY)) == -1) {
fprintf(stderr, "%s: %s requests unknown path %s\n", tstamp(), name, reqbuf);
if(responsehdr(HttpNotFound) != -1
&& responsecontenttype(texthtml) != -1
&& writedata("\r\n<html><body>404 Not Found</body></html>\r\n") != -1);
}
else {
if((p = strrchr(reqbuf, '.'))) {
p++;
for(i = 0; i < LENGTH(servermimes); i++)
if(!strcmp(servermimes[i].extension, p)) {
mimetype = servermimes[i].mimetype;
break;
}
}
if(responsehdr(HttpOk) != -1
&& responsecontentlen(st.st_size) != -1
&& responsecontenttype(mimetype) != -1
&& writedata("\r\n") != -1)
responsefiledata(ffd, st.st_size);
close(ffd);
}
}
void
responsedirdata(DIR *d) {
struct dirent *e;
if(responsehdr(HttpOk) != -1
&& responsecontenttype(texthtml) != -1
&& writedata("\r\n<html><body><a href='..'>..</a><br>\r\n") != -1);
else
return;
while((e = readdir(d))) {
if(e->d_name[0] == '.') /* ignore hidden files, ., .. */
continue;
if(snprintf(respbuf, sizeof respbuf, "<a href='%s%s'>%s</a><br>\r\n",
reqbuf, e->d_name, e->d_name) >= sizeof respbuf)
{
logerrmsg("snprintf failed, buffer sizeof exceeded");
return;
}
if(writedata(respbuf) == -1)
return;
}
writedata("</body></html>\r\n");
}
void
responsedir(void) {
ssize_t len = strlen(reqbuf);
DIR *d;
if((reqbuf[len - 1] != '/') && (len + 1 < MAXREQLEN - 1)) {
/* add directory terminator if necessary */
reqbuf[len++] = '/';
reqbuf[len] = 0;
fprintf(stdout, "%s: redirecting %s to %s%s\n", tstamp(), name, location, reqbuf);
if(responsehdr(HttpMoved) != -1
&& responselocation(location, reqbuf) != -1
&& responsecontenttype(texthtml) != -1
&& writedata("\r\n<html><body>301 Moved Permanently</a></body></html>\r\n") != -1);
return;
}
if(len + strlen(docindex) + 1 < MAXREQLEN - 1)
memcpy(reqbuf + len, docindex, strlen(docindex) + 1);
if(access(reqbuf, R_OK) == -1) { /* directory mode */
reqbuf[len] = 0; /* cut off docindex again */
if((d = opendir(reqbuf))) {
responsedirdata(d);
closedir(d);
}
}
else
responsefile(); /* docindex */
}
void
response(void) {
char *p;
struct stat st;
for(p = reqbuf; *p; p++)
if(*p == '\\' || (*p == '/' && *(p + 1) == '.')) { /* don't serve bogus or hidden files */
fprintf(stderr, "%s: %s requests bogus or hidden file %s\n", tstamp(), name, reqbuf);
if(responsehdr(HttpUnauthorized) != -1
&& responsecontenttype(texthtml) != -1
&& writedata("\r\n<html><body>401 Unauthorized</body></html>\r\n") != -1);
return;
}
fprintf(stdout, "%s: %s requests: %s\n", tstamp(), name, reqbuf);
stat(reqbuf, &st);
if(S_ISDIR(st.st_mode))
responsedir();
else
responsefile();
}
int
request(void) {
char *p, *res;
int r, ishead = 0;
if((r = read(cfd, reqbuf, (MAXREQLEN - 1))) < 0) {
fprintf(stderr, "%s: read: %s\n", tstamp(), strerror(errno));
return -1;
}
for(p = reqbuf; p < reqbuf + MAXREQLEN && *p != '\r' && *p != '\n'; p++);
if(*p == '\r' || *p == '\n') {
*p = 0;
/* parse command */
if(strncmp(reqbuf, "GET ", 4)) {
fprintf(stderr, "%s: %s performs unsupported request %s\n", tstamp(), name, reqbuf);
return -1;
}
if(reqbuf[4] != '/') {
fprintf(stderr, "%s: %s performs invalid request %s\n", tstamp(), name, reqbuf);
return -1;
}
}
/* determine path */
for(res = reqbuf + 4; *res && *(res + 1) == '/'; res++);
for(p = res; *p && *p != ' '; p++);
*p = 0;
memmove(reqbuf, res, (p - res) + 1);
return 0;
}
void
serve(int fd) {
int result;
socklen_t salen;
struct sockaddr sa;
salen = sizeof sa;
while(running) {
if((cfd = accept(fd, &sa, &salen)) == -1)
break;
if(fork() == 0) {
close(fd);
name[0] = 0;
getnameinfo(&sa, salen, name, sizeof name, NULL, 0, NI_NOFQDN);
result = request();
shutdown(cfd, SHUT_RD);
if(result == 0)
response();
shutdown(cfd, SHUT_WR);
close(cfd);
exit(EXIT_SUCCESS);
}
}
fprintf(stdout, "%s: shutting down\n", tstamp());
}
void
sighandler(int sig) {
switch(sig) {
default: break;
case SIGHUP:
case SIGINT:
case SIGQUIT:
case SIGABRT:
case SIGTERM:
close(fd);
running = 0;
break;
case SIGCHLD:
while(0 < waitpid(-1, NULL, WNOHANG));
break;
}
}
char *
tstamp(void) {
static char res[25];
time_t t = time(NULL);
memcpy(res, asctime(gmtime(&t)), 24);
res[24] = 0;
return res;
}
int
main(int argc, char *argv[]) {
struct addrinfo hints, *ai;
struct passwd *upwd, *gpwd;
int i;
/* arguments */
for(i = 1; i < argc; i++)
if(!strcmp(argv[i], "-v"))
die("quark-"VERSION", © 2009 Anselm R Garbe\n");
else
die("usage: quark [-v]\n");
/* sanity checks */
if(!(upwd = getpwnam(user)))
die("error: invalid user %s\n", user);
if(!(gpwd = getpwnam(group)))
die("error: invalid group %s\n", group);
/* 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)))
die("error: getaddrinfo: %s\n", gai_strerror(i));
if((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
freeaddrinfo(ai);
die("error: socket: %s\n", strerror(errno));
}
if(bind(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
close(fd);
freeaddrinfo(ai);
die("error: bind: %s\n", strerror(errno));
}
if(listen(fd, SOMAXCONN) == -1) {
close(fd);
freeaddrinfo(ai);
die("error: listen: %s\n", strerror(errno));
}
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) {
close(fd);
freeaddrinfo(ai);
die("error: location too long\n");
}
signal(SIGCHLD, sighandler);
signal(SIGHUP, sighandler);
signal(SIGINT, sighandler);
signal(SIGQUIT, sighandler);
signal(SIGABRT, sighandler);
signal(SIGTERM, sighandler);
signal(SIGKILL, sighandler);
if(chroot(docroot) == -1)
die("error: chroot %s: %s\n", docroot, strerror(errno));
if(setgid(gpwd->pw_gid) == -1)
die("error: cannot set group id\n");
if(setuid(upwd->pw_uid) == -1)
die("error: cannot set user id\n");
if(getuid() == 0)
die("error: won't run with root permissions, choose another user\n");
if(getgid() == 0)
die("error: won't run with root permissions, choose another group\n");
fprintf(stdout, "%s: listening on %s:%s using %s as root directory\n", tstamp(), servername, serverport, docroot);
serve(fd); /* main loop */
freeaddrinfo(ai);
return 0;
}