commit ce31aca1aad09abb8a4d22f58859f004348a104c
Author: Anselm R Garbe <anselm@garbe.us>
Date: Sat, 15 Aug 2009 19:56:11 +0100
initial commit
Diffstat:
A | LICENSE | | | 21 | +++++++++++++++++++++ |
A | Makefile | | | 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README | | | 19 | +++++++++++++++++++ |
A | config.h | | | 23 | +++++++++++++++++++++++ |
A | config.mk | | | 21 | +++++++++++++++++++++ |
A | quark.c | | | 432 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 568 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -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.
diff --git a/Makefile b/Makefile
@@ -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
diff --git a/README b/README
@@ -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.
diff --git a/config.h b/config.h
@@ -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" },
+};
diff --git a/config.mk b/config.mk
@@ -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
diff --git a/quark.c b/quark.c
@@ -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;
+}