diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7cd929a..0000000 --- a/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -MIT/X Consortium License - -© 2009-2011 Anselm R Garbe -© 2011 Szabolcs Nagy -© 2014 Hiltjo Posthuma -© 2014 Laslo Hunhold - -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 deleted file mode 100644 index 92a2a61..0000000 --- a/Makefile +++ /dev/null @@ -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 diff --git a/README b/README deleted file mode 100644 index fae4a21..0000000 --- a/README +++ /dev/null @@ -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 - $ sudo quark diff --git a/arg.h b/arg.h deleted file mode 100644 index 4df77a7..0000000 --- a/arg.h +++ /dev/null @@ -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 diff --git a/config.def.h b/config.def.h deleted file mode 100644 index 0dc9cb1..0000000 --- a/config.def.h +++ /dev/null @@ -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" } -}; diff --git a/config.mk b/config.mk deleted file mode 100644 index 9368965..0000000 --- a/config.mk +++ /dev/null @@ -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 diff --git a/quark.1 b/quark.1 deleted file mode 100644 index 35f854c..0000000 --- a/quark.1 +++ /dev/null @@ -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! diff --git a/quark.c b/quark.c deleted file mode 100644 index 9c8928d..0000000 --- a/quark.c +++ /dev/null @@ -1,750 +0,0 @@ -/* See LICENSE file for license details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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"HttpNotFound"\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..
\r\n")) - return; - for (n = 0; n < len; n++) { - if (e[n]->d_name[0] == '.') /* ignore hidden files, ., .. */ - continue; - if (snprintf(resbuf, MAXBUFLEN, "%s
\r\n", - reqbuf, e[n]->d_name, e[n]->d_name) >= MAXBUFLEN) - { - logerrmsg("snprintf failed, buffer sizeof exceeded"); - return; - } - if (writetext(resbuf)) - return; - } - writetext("\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"HttpMoved"\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"HttpForbidden"\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"HttpNotFound"\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"HttpForbidden"\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; -}