summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJan Kneschke <jan@kneschke.de>2005-02-20 14:27:00 +0000
committerJan Kneschke <jan@kneschke.de>2005-02-20 14:27:00 +0000
commitbcdc6a3bbcde8e66da41aa2311642e53f4fc7c9b (patch)
treea0536d23ba17a40c236fc3cd2a4a133110ae7501 /src
downloadlighttpd-git-bcdc6a3bbcde8e66da41aa2311642e53f4fc7c9b.tar.gz
moved everything below trunk/ and added branches/ and tags/
git-svn-id: svn://svn.lighttpd.net/lighttpd/trunk@30 152afb58-edef-0310-8abb-c4023f1b3aa9
Diffstat (limited to 'src')
-rw-r--r--src/.cvsignore11
-rw-r--r--src/Makefile.am221
-rw-r--r--src/array.c241
-rw-r--r--src/array.h120
-rw-r--r--src/base.h522
-rw-r--r--src/bitset.c66
-rw-r--r--src/bitset.h19
-rw-r--r--src/buffer.c1106
-rw-r--r--src/buffer.h122
-rw-r--r--src/chunk.c312
-rw-r--r--src/chunk.h56
-rw-r--r--src/config.c1025
-rw-r--r--src/configfile.h18
-rw-r--r--src/configparser.h19
-rw-r--r--src/configparser.y164
-rw-r--r--src/connections.c1585
-rw-r--r--src/connections.h19
-rw-r--r--src/crc32.c84
-rw-r--r--src/crc32.h6
-rw-r--r--src/data_array.c56
-rw-r--r--src/data_config.c69
-rw-r--r--src/data_count.c56
-rw-r--r--src/data_fastcgi.c56
-rw-r--r--src/data_integer.c53
-rw-r--r--src/data_string.c92
-rw-r--r--src/etag.c30
-rw-r--r--src/etag.h15
-rw-r--r--src/fastcgi.h136
-rw-r--r--src/fdevent.c202
-rw-r--r--src/fdevent.h222
-rw-r--r--src/fdevent_freebsd_kqueue.c194
-rw-r--r--src/fdevent_linux_rtsig.c260
-rw-r--r--src/fdevent_linux_sysepoll.c152
-rw-r--r--src/fdevent_poll.c182
-rw-r--r--src/fdevent_select.c127
-rw-r--r--src/fdevent_solaris_devpoll.c141
-rw-r--r--src/file_cache.c483
-rw-r--r--src/file_cache.h12
-rw-r--r--src/http_auth.c939
-rw-r--r--src/http_auth.h64
-rw-r--r--src/http_auth_digest.c103
-rw-r--r--src/http_auth_digest.h46
-rw-r--r--src/http_chunk.c133
-rw-r--r--src/http_chunk.h12
-rw-r--r--src/inet_ntop_cache.c53
-rw-r--r--src/inet_ntop_cache.h7
-rw-r--r--src/joblist.c63
-rw-r--r--src/joblist.h13
-rw-r--r--src/keyvalue.c352
-rw-r--r--src/keyvalue.h80
-rw-r--r--src/lemon.c4397
-rw-r--r--src/lempar.c687
-rw-r--r--src/log.c238
-rw-r--r--src/log.h13
-rw-r--r--src/md5.c356
-rw-r--r--src/md5.h37
-rw-r--r--src/md5_global.h31
-rw-r--r--src/mod_access.c177
-rw-r--r--src/mod_accesslog.c840
-rw-r--r--src/mod_alias.c185
-rw-r--r--src/mod_auth.c565
-rw-r--r--src/mod_auth.h0
-rw-r--r--src/mod_cgi.c1191
-rw-r--r--src/mod_compress.c744
-rw-r--r--src/mod_evhost.c340
-rw-r--r--src/mod_expire.c356
-rw-r--r--src/mod_fastcgi.c3209
-rw-r--r--src/mod_mysql_vhost.c425
-rw-r--r--src/mod_proxy.c1093
-rw-r--r--src/mod_redirect.c271
-rw-r--r--src/mod_rewrite.c453
-rw-r--r--src/mod_rrdtool.c457
-rw-r--r--src/mod_secure_download.c323
-rw-r--r--src/mod_setenv.c215
-rw-r--r--src/mod_simple_vhost.c284
-rw-r--r--src/mod_skeleton.c220
-rw-r--r--src/mod_ssi.c1073
-rw-r--r--src/mod_ssi.h43
-rw-r--r--src/mod_ssi_expr.c324
-rw-r--r--src/mod_ssi_expr.h31
-rw-r--r--src/mod_ssi_exprparser.h12
-rw-r--r--src/mod_ssi_exprparser.y119
-rw-r--r--src/mod_status.c653
-rw-r--r--src/mod_userdir.c248
-rw-r--r--src/mod_usertrack.c247
-rw-r--r--src/network.c517
-rw-r--r--src/network.h13
-rw-r--r--src/network_backends.h54
-rw-r--r--src/network_freebsd_sendfile.c192
-rw-r--r--src/network_linux_sendfile.c205
-rw-r--r--src/network_openssl.c176
-rw-r--r--src/network_solaris_sendfilev.c208
-rw-r--r--src/network_write.c188
-rw-r--r--src/network_writev.c288
-rw-r--r--src/plugin.c433
-rw-r--r--src/plugin.h91
-rw-r--r--src/request.c937
-rw-r--r--src/request.h9
-rw-r--r--src/response.c1432
-rw-r--r--src/response.h18
-rw-r--r--src/server.c1021
-rw-r--r--src/server.h17
-rw-r--r--src/settings.h46
-rw-r--r--src/spawn-fcgi.c346
-rw-r--r--src/stream.c100
-rw-r--r--src/stream.h14
-rw-r--r--src/sys-mmap.h24
-rw-r--r--src/sys-socket.h24
108 files changed, 36329 insertions, 0 deletions
diff --git a/src/.cvsignore b/src/.cvsignore
new file mode 100644
index 00000000..82d670f2
--- /dev/null
+++ b/src/.cvsignore
@@ -0,0 +1,11 @@
+lemon
+lighttpd
+Makefile.in
+Makefile
+spawn-fcgi
+chunk
+.deps
+.libs
+array
+mod_ssi_exprparser.c
+configparser.c
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..c4d275fc
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,221 @@
+noinst_PROGRAMS=array chunk lemon # simple-fcgi #graphic evalo bench ajp ssl error_test adserver gen-license
+sbin_PROGRAMS=lighttpd
+bin_PROGRAMS=spawn-fcgi
+LEMON=$(top_builddir)/src/lemon
+
+lemon_SOURCES=lemon.c
+
+#simple_fcgi_SOURCES=simple-fcgi.c
+#simple_fcgi_LDADD=-lfcgi
+
+if CROSS_COMPILING
+configparser.c configparser.h:
+mod_ssi_exprparser.c mod_ssi_exprparser.h:
+else
+configparser.y: lemon
+mod_ssi_exprparser.y: lemon
+
+configparser.c configparser.h: configparser.y
+ rm -f configparser.h
+ $(LEMON) -q $(srcdir)/configparser.y $(srcdir)/lempar.c
+
+mod_ssi_exprparser.c mod_ssi_exprparser.h: mod_ssi_exprparser.y
+ rm -f mod_ssi_exprparser.h
+ $(LEMON) -q $(srcdir)/mod_ssi_exprparser.y $(srcdir)/lempar.c
+endif
+
+config.c: configparser.h
+mod_ssi_expr.c: mod_ssi_exprparser.h
+
+common_src=buffer.c log.c \
+ keyvalue.c response.c request.c chunk.c \
+ http_chunk.c stream.c fdevent.c connections.c \
+ file_cache.c plugin.c joblist.c etag.c array.c \
+ data_string.c data_count.c data_array.c \
+ data_integer.c md5.c data_fastcgi.c \
+ fdevent_select.c fdevent_linux_rtsig.c \
+ fdevent_poll.c fdevent_linux_sysepoll.c \
+ fdevent_solaris_devpoll.c fdevent_freebsd_kqueue.c \
+ network_write.c network_linux_sendfile.c \
+ network_freebsd_sendfile.c network_writev.c \
+ network_solaris_sendfilev.c network_openssl.c \
+ data_config.c bitset.c configparser.c config.c \
+ inet_ntop_cache.c network.c
+
+src = server.c
+
+spawn_fcgi_SOURCES=spawn-fcgi.c
+
+lib_LTLIBRARIES =
+
+if NO_RDYNAMIC
+# if the linker doesn't allow referencing symbols of the binary
+# we have to put everything into a shared-lib and link it into
+# everything
+lib_LTLIBRARIES += liblightcomp.la
+liblightcomp_la_SOURCES=$(common_src)
+liblightcomp_la_CFLAGS=$(AM_CFLAGS)
+liblightcomp_la_LDFLAGS = -avoid-version -no-undefined
+liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB)
+common_libadd = liblightcomp.la
+else
+src += $(common_src)
+common_libadd =
+endif
+
+lib_LTLIBRARIES += mod_mysql_vhost.la
+mod_mysql_vhost_la_SOURCES = mod_mysql_vhost.c
+mod_mysql_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_mysql_vhost_la_LIBADD = $(MYSQL_LIBS) $(common_libadd)
+
+lib_LTLIBRARIES += mod_cgi.la
+mod_cgi_la_SOURCES = mod_cgi.c
+mod_cgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_cgi_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_setenv.la
+mod_setenv_la_SOURCES = mod_setenv.c
+mod_setenv_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_setenv_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_alias.la
+mod_alias_la_SOURCES = mod_alias.c
+mod_alias_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_alias_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_userdir.la
+mod_userdir_la_SOURCES = mod_userdir.c
+mod_userdir_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_userdir_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_rrdtool.la
+mod_rrdtool_la_SOURCES = mod_rrdtool.c
+mod_rrdtool_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_rrdtool_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_usertrack.la
+mod_usertrack_la_SOURCES = mod_usertrack.c
+mod_usertrack_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_usertrack_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_proxy.la
+mod_proxy_la_SOURCES = mod_proxy.c
+mod_proxy_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_proxy_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_ssi.la
+mod_ssi_la_SOURCES = mod_ssi_exprparser.c mod_ssi_expr.c mod_ssi.c
+mod_ssi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_ssi_la_LIBADD = $(common_libadd) $(PCRE_LIB)
+
+lib_LTLIBRARIES += mod_secdownload.la
+mod_secdownload_la_SOURCES = mod_secure_download.c
+mod_secdownload_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_secdownload_la_LIBADD = $(common_libadd)
+
+#lib_LTLIBRARIES += mod_httptls.la
+#mod_httptls_la_SOURCES = mod_httptls.c
+#mod_httptls_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+#mod_httptls_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_expire.la
+mod_expire_la_SOURCES = mod_expire.c
+mod_expire_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_expire_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_evhost.la
+mod_evhost_la_SOURCES = mod_evhost.c
+mod_evhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_evhost_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_simple_vhost.la
+mod_simple_vhost_la_SOURCES = mod_simple_vhost.c
+mod_simple_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_simple_vhost_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_fastcgi.la
+mod_fastcgi_la_SOURCES = mod_fastcgi.c
+mod_fastcgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_fastcgi_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_access.la
+mod_access_la_SOURCES = mod_access.c
+mod_access_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_access_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_compress.la
+mod_compress_la_SOURCES = mod_compress.c crc32.c
+mod_compress_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_compress_la_LIBADD = $(Z_LIB) $(BZ_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_auth.la
+mod_auth_la_SOURCES = mod_auth.c http_auth_digest.c http_auth.c
+mod_auth_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_auth_la_LIBADD = $(CRYPT_LIB) $(LDAP_LIB) $(LBER_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_rewrite.la
+mod_rewrite_la_SOURCES = mod_rewrite.c
+mod_rewrite_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_rewrite_la_LIBADD = $(PCRE_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_redirect.la
+mod_redirect_la_SOURCES = mod_redirect.c
+mod_redirect_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_redirect_la_LIBADD = $(PCRE_LIB) $(common_libadd)
+
+lib_LTLIBRARIES += mod_status.la
+mod_status_la_SOURCES = mod_status.c
+mod_status_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_status_la_LIBADD = $(common_libadd)
+
+lib_LTLIBRARIES += mod_accesslog.la
+mod_accesslog_la_SOURCES = mod_accesslog.c
+mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
+mod_accesslog_la_LIBADD = $(common_libadd)
+
+
+hdr = server.h buffer.h network.h log.h keyvalue.h \
+ response.h request.h fastcgi.h chunk.h \
+ settings.h http_chunk.h http_auth_digest.h \
+ md5.h md5_global.h http_auth.h stream.h \
+ fdevent.h connections.h base.h file_cache.h \
+ plugin.h mod_auth.h \
+ etag.h joblist.h array.h crc32.h \
+ network_backends.h configfile.h bitset.h \
+ mod_ssi.h mod_ssi_expr.h inet_ntop_cache.h \
+ configparser.h mod_ssi_exprparser.h \
+ sys-mmap.h sys-socket.h
+ # license.h
+
+#hdr += chat.h chat_misc.h chat_endec.h chat_user.h \
+# chat_channel.h
+
+DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\""
+
+lighttpd_SOURCES = $(src)
+lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB)
+lighttpd_LDFLAGS = -export-dynamic
+lighttpd_CCPFLAGS = $(MYSQL_INCLUDES)
+
+array_SOURCES = array.c buffer.c data_string.c data_count.c
+array_CPPFLAGS= -DDEBUG_ARRAY
+
+chunk_SOURCES = buffer.c chunk.c
+chunk_CPPFLAGS= -DDEBUG_CHUNK
+
+#gen_license_SOURCES = license.c md5.c buffer.c gen_license.c
+
+#ssl_SOURCES = ssl.c
+
+
+#adserver_SOURCES = buffer.c iframe.c
+#adserver_LDADD = -lfcgi -lmysqlclient
+
+#error_test_SOURCES = error_test.c
+
+#evalo_SOURCES = buffer.c eval.c
+#bench_SOURCES = buffer.c bench.c
+#ajp_SOURCES = ajp.c
+
+noinst_HEADERS = $(hdr)
+EXTRA_DIST = mod_skeleton.c configparser.y mod_ssi_exprparser.y lempar.c
diff --git a/src/array.c b/src/array.c
new file mode 100644
index 00000000..a57abb36
--- /dev/null
+++ b/src/array.c
@@ -0,0 +1,241 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <errno.h>
+#include <assert.h>
+
+#include "array.h"
+#include "buffer.h"
+
+array *array_init(void) {
+ array *a;
+
+ a = calloc(1, sizeof(*a));
+ assert(a);
+
+ a->next_power_of_2 = 1;
+
+ return a;
+}
+
+void array_free(array *a) {
+ size_t i;
+ if (!a) return;
+
+ for (i = 0; i < a->size; i++) {
+ if (a->data[i]) a->data[i]->free(a->data[i]);
+ }
+
+ if (a->data) free(a->data);
+ if (a->sorted) free(a->sorted);
+
+ free(a);
+}
+
+void array_reset(array *a) {
+ size_t i;
+ if (!a) return;
+
+ for (i = 0; i < a->used; i++) {
+ a->data[i]->reset(a->data[i]);
+ }
+
+ a->used = 0;
+}
+
+static int array_get_index(array *a, const char *key, size_t keylen, int *rndx) {
+ int ndx = -1;
+ int i, pos = 0;
+
+ if (key == NULL) return -1;
+
+ /* try to find the string */
+ for (i = pos = a->next_power_of_2 / 2; ; i >>= 1) {
+ int cmp;
+
+ if (pos < 0) {
+ pos += i;
+ } else if (pos >= (int)a->used) {
+ pos -= i;
+ } else {
+ cmp = buffer_caseless_compare(key, keylen, a->data[a->sorted[pos]]->key->ptr, a->data[a->sorted[pos]]->key->used);
+
+ if (cmp == 0) {
+ /* found */
+ ndx = a->sorted[pos];
+ break;
+ } else if (cmp < 0) {
+ pos -= i;
+ } else {
+ pos += i;
+ }
+ }
+ if (i == 0) break;
+ }
+
+ if (rndx) *rndx = pos;
+
+ return ndx;
+}
+
+data_unset *array_get_element(array *a, const char *key) {
+ int ndx;
+
+ if (-1 != (ndx = array_get_index(a, key, strlen(key) + 1, NULL))) {
+ /* found, leave here */
+
+ return a->data[ndx];
+ }
+
+ return NULL;
+}
+
+data_unset *array_get_unused_element(array *a, data_type_t t) {
+ data_unset *ds = NULL;
+
+ UNUSED(t);
+
+ if (a->size == 0) return NULL;
+
+ if (a->used == a->size) return NULL;
+
+ if (a->data[a->used]) {
+ ds = a->data[a->used];
+
+ a->data[a->used] = NULL;
+ }
+
+ return ds;
+}
+
+int array_insert_unique(array *a, data_unset *str) {
+ int ndx = -1;
+ int pos = 0;
+ size_t j;
+
+ /* generate unique index if neccesary */
+ if (str->key->used == 0) {
+ buffer_copy_long(str->key, a->unique_ndx++);
+ }
+
+ /* try to find the string */
+
+ if (-1 != (ndx = array_get_index(a, str->key->ptr, str->key->used, &pos))) {
+ /* found, leave here */
+ if (a->data[ndx]->type == str->type) {
+ str->insert_dup(a->data[ndx], str);
+ } else {
+ fprintf(stderr, "a\n");
+ }
+ return 0;
+ }
+
+ /* insert */
+
+ if (a->used+1 > INT_MAX) {
+ /* we can't handle more then INT_MAX entries: see array_get_index() */
+ return -1;
+ }
+
+ if (a->size == 0) {
+ a->size = 16;
+ a->data = malloc(sizeof(*a->data) * a->size);
+ a->sorted = malloc(sizeof(*a->sorted) * a->size);
+ assert(a->data);
+ assert(a->sorted);
+ for (j = a->used; j < a->size; j++) a->data[j] = NULL;
+ } else if (a->size == a->used) {
+ a->size += 16;
+ a->data = realloc(a->data, sizeof(*a->data) * a->size);
+ a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size);
+ assert(a->data);
+ assert(a->sorted);
+ for (j = a->used; j < a->size; j++) a->data[j] = NULL;
+ }
+
+ ndx = (int) a->used;
+
+ a->data[a->used++] = str;
+
+ if (pos != ndx &&
+ ((pos < 0) ||
+ buffer_caseless_compare(str->key->ptr, str->key->used, a->data[a->sorted[pos]]->key->ptr, a->data[a->sorted[pos]]->key->used) > 0)) {
+ pos++;
+ }
+
+ /* move everything on step to the right */
+ if (pos != ndx) {
+ memmove(a->sorted + (pos + 1), a->sorted + (pos), (ndx - pos) * sizeof(*a->sorted));
+ }
+
+ /* insert */
+ a->sorted[pos] = ndx;
+
+ if (a->next_power_of_2 == (size_t)ndx) a->next_power_of_2 <<= 1;
+
+ return 0;
+}
+
+int array_print(array *a) {
+ size_t i;
+
+ for (i = 0; i < a->used; i++) {
+ fprintf(stderr, "%d: ", i);
+ a->data[i]->print(a->data[i]);
+ fprintf(stderr, "\n");
+ }
+
+ return 0;
+}
+
+#ifdef DEBUG_ARRAY
+int main (int argc, char **argv) {
+ array *a;
+ data_string *ds;
+ data_count *dc;
+
+ UNUSED(argc);
+ UNUSED(argv);
+
+ a = array_init();
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key, "abc");
+ buffer_copy_string(ds->value, "alfrag");
+
+ array_insert_unique(a, (data_unset *)ds);
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key, "abc");
+ buffer_copy_string(ds->value, "hameplman");
+
+ array_insert_unique(a, (data_unset *)ds);
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key, "123");
+ buffer_copy_string(ds->value, "alfrag");
+
+ array_insert_unique(a, (data_unset *)ds);
+
+ dc = data_count_init();
+ buffer_copy_string(dc->key, "def");
+
+ array_insert_unique(a, (data_unset *)dc);
+
+ dc = data_count_init();
+ buffer_copy_string(dc->key, "def");
+
+ array_insert_unique(a, (data_unset *)dc);
+
+ array_print(a);
+
+ array_free(a);
+
+ fprintf(stderr, "%d\n",
+ buffer_caseless_compare(CONST_STR_LEN("Content-Type"), CONST_STR_LEN("Content-type")));
+
+ return 0;
+}
+#endif
diff --git a/src/array.h b/src/array.h
new file mode 100644
index 00000000..8ac2bdcc
--- /dev/null
+++ b/src/array.h
@@ -0,0 +1,120 @@
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#include <stdlib.h>
+#include "config.h"
+#ifdef HAVE_PCRE_H
+# include <pcre.h>
+#endif
+#include "buffer.h"
+
+#define DATA_IS_STRING(x) (x->type == TYPE_STRING)
+
+typedef enum { TYPE_UNSET, TYPE_STRING, TYPE_COUNT, TYPE_ARRAY, TYPE_INTEGER, TYPE_FASTCGI, TYPE_CONFIG } data_type_t;
+#define DATA_UNSET \
+ data_type_t type; \
+ buffer *key; \
+ void (* free)(struct data_unset *p); \
+ void (* reset)(struct data_unset *p); \
+ int (*insert_dup)(struct data_unset *dst, struct data_unset *src); \
+ void (*print)(struct data_unset *p)
+
+typedef struct data_unset {
+ DATA_UNSET;
+} data_unset;
+
+typedef struct {
+ data_unset **data;
+
+ size_t *sorted;
+
+ size_t used;
+ size_t size;
+
+ size_t unique_ndx;
+
+ size_t next_power_of_2;
+} array;
+
+typedef struct {
+ DATA_UNSET;
+
+ int count;
+} data_count;
+
+data_count *data_count_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ buffer *value;
+} data_string;
+
+data_string *data_string_init(void);
+data_string *data_response_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ array *value;
+} data_array;
+
+data_array *data_array_init(void);
+
+typedef enum { CONFIG_COND_UNSET, CONFIG_COND_EQ, CONFIG_COND_MATCH, CONFIG_COND_NE, CONFIG_COND_NOMATCH } config_cond_t;
+
+/* $HTTP["host"] == "incremental.home.kneschke.de" { ... }
+ * comp_key cond string/regex
+ */
+
+typedef struct {
+ DATA_UNSET;
+
+ array *value;
+
+ buffer *comp_key;
+
+ config_cond_t cond;
+
+ union {
+ buffer *string;
+#ifdef HAVE_PCRE_H
+ pcre *regex;
+#endif
+ } match;
+} data_config;
+
+data_config *data_config_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ int value;
+} data_integer;
+
+data_integer *data_integer_init(void);
+
+typedef struct {
+ DATA_UNSET;
+
+ buffer *host;
+
+ unsigned short port;
+ int usage;
+
+ time_t disable_ts;
+
+} data_fastcgi;
+
+data_fastcgi *data_fastcgi_init(void);
+
+array *array_init(void);
+void array_free(array *a);
+void array_reset(array *a);
+int array_insert_unique(array *a, data_unset *str);
+int array_print(array *a);
+data_unset *array_get_unused_element(array *a, data_type_t t);
+data_unset *array_get_element(array *a, const char *key);
+int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len);
+
+#endif
diff --git a/src/base.h b/src/base.h
new file mode 100644
index 00000000..a9b483d7
--- /dev/null
+++ b/src/base.h
@@ -0,0 +1,522 @@
+#ifndef _BASE_H_
+#define _BASE_H_
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include "config.h"
+
+#include <limits.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+#include "buffer.h"
+#include "array.h"
+#include "chunk.h"
+#include "keyvalue.h"
+#include "settings.h"
+#include "fdevent.h"
+#include "sys-socket.h"
+
+
+#if defined HAVE_LIBSSL && defined HAVE_OPENSSL_SSL_H
+# define USE_OPENSSL
+# include <openssl/ssl.h>
+#endif
+
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+
+#ifndef SIZE_MAX
+# ifdef SIZE_T_MAX
+# define SIZE_MAX SIZE_T_MAX
+# else
+# define SIZE_MAX ((size_t)~0)
+# endif
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((size_t)~0 >> 1)
+#endif
+
+#ifdef __APPLE__
+#include <crt_externs.h>
+#define environ (* _NSGetEnviron())
+#else
+extern char **environ;
+#endif
+
+/* for solaris 2.5 and NetBSD 1.3.x */
+#ifndef HAVE_SOCKLEN_T
+typedef int socklen_t;
+#endif
+
+/* solaris and NetBSD 1.3.x again */
+#if (!defined(HAVE_STDINT_H)) && (!defined(HAVE_INTTYPES_H)) && (!defined(uint32_t))
+# define uint32_t u_int32_t
+#endif
+
+
+#ifndef SHUT_WR
+# define SHUT_WR 1
+#endif
+
+#include "settings.h"
+
+typedef enum { T_CONFIG_UNSET,
+ T_CONFIG_STRING,
+ T_CONFIG_SHORT,
+ T_CONFIG_BOOLEAN,
+ T_CONFIG_ARRAY,
+ T_CONFIG_LOCAL,
+ T_CONFIG_DEPRECATED
+} config_values_type_t;
+
+typedef enum { T_CONFIG_SCOPE_UNSET,
+ T_CONFIG_SCOPE_SERVER,
+ T_CONFIG_SCOPE_CONNECTION
+} config_scope_type_t;
+
+typedef struct {
+ const char *key;
+ void *destination;
+
+ config_values_type_t type;
+ config_scope_type_t scope;
+} config_values_t;
+
+typedef enum { DIRECT, EXTERNAL } connection_type;
+
+typedef struct {
+ char *key;
+ connection_type type;
+ char *value;
+} request_handler;
+
+typedef struct {
+ char *key;
+ char *host;
+ unsigned short port;
+ int used;
+ short factor;
+} fcgi_connections;
+
+
+typedef union {
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 ipv6;
+#endif
+ struct sockaddr_in ipv4;
+ struct sockaddr plain;
+} sock_addr;
+
+/* fcgi_response_header contains ... */
+#define HTTP_STATUS BV(0)
+#define HTTP_CONNECTION BV(1)
+#define HTTP_CONTENT_LENGTH BV(2)
+#define HTTP_DATE BV(3)
+#define HTTP_LOCATION BV(4)
+
+typedef struct {
+ /** HEADER */
+ /* the request-line */
+ buffer *request;
+ buffer *uri;
+
+ buffer *orig_uri;
+
+ http_method_t http_method;
+ http_version_t http_version;
+
+ buffer *request_line;
+
+ /* strings to the header */
+ buffer *http_host; /* not alloced */
+ const char *http_range;
+ const char *http_content_type;
+ const char *http_if_modified_since;
+ const char *http_if_none_match;
+
+ array *headers;
+
+ /* CONTENT */
+ buffer *content;
+ size_t content_length; /* returned by strtoul() */
+
+ /* internal representation */
+ int accept_encoding;
+
+ /* internal */
+ buffer *pathinfo;
+} request;
+
+typedef struct {
+ off_t content_length;
+ int keep_alive; /* used by the subrequests in proxy, cgi and fcgi to say the subrequest was keep-alive or not */
+
+ array *headers;
+
+ enum {
+ HTTP_TRANSFER_ENCODING_IDENTITY, HTTP_TRANSFER_ENCODING_CHUNKED
+ } transfer_encoding;
+} response;
+
+typedef struct {
+ buffer *name;
+ buffer *etag;
+
+ struct stat st;
+
+ int fd;
+ int fde_ndx;
+
+ char *mmap_p;
+ size_t mmap_length;
+ off_t mmap_offset;
+
+ size_t in_use;
+ size_t is_dirty;
+
+ time_t stat_ts;
+ buffer *content_type;
+} file_cache_entry;
+
+typedef struct {
+ buffer *scheme;
+ buffer *authority;
+ buffer *path;
+ buffer *path_raw;
+ buffer *query;
+} request_uri;
+
+typedef struct {
+ buffer *path;
+
+ buffer *doc_root; /* path = doc_root + rel_path */
+ buffer *rel_path;
+
+ buffer *etag;
+} physical;
+
+typedef struct {
+ file_cache_entry **ptr;
+
+ size_t size;
+ size_t used;
+
+ buffer *dir_name;
+} file_cache;
+
+typedef struct {
+ array *indexfiles;
+ array *mimetypes;
+
+ /* virtual-servers */
+ buffer *document_root;
+ buffer *server_name;
+ buffer *error_handler;
+ buffer *server_tag;
+
+ unsigned short dir_listing;
+ unsigned short max_keep_alive_requests;
+ unsigned short max_keep_alive_idle;
+ unsigned short max_read_idle;
+ unsigned short max_write_idle;
+ unsigned short use_xattr;
+ unsigned short follow_symlink;
+
+ /* debug */
+
+ unsigned short log_file_not_found;
+ unsigned short log_request_header;
+ unsigned short log_request_handling;
+ unsigned short log_response_header;
+
+
+ /* server wide */
+ buffer *ssl_pemfile;
+ unsigned short use_ipv6;
+ unsigned short is_ssl;
+ unsigned short allow_http11;
+ unsigned short max_request_size;
+
+ unsigned short kbytes_per_second; /* connection kb/s limit */
+
+ /* configside */
+ unsigned short global_kbytes_per_second; /* */
+
+ off_t global_bytes_per_second_cnt;
+ /* server-wide traffic-shaper
+ *
+ * each context has the counter which is inited once
+ * a second by the global_kbytes_per_second config-var
+ *
+ * as soon as global_kbytes_per_second gets below 0
+ * the connected conns are "offline" a little bit
+ *
+ * the problem:
+ * we somehow have to loose our "we are writable" signal
+ * on the way.
+ *
+ */
+ off_t *global_bytes_per_second_cnt_ptr; /* */
+
+#ifdef USE_OPENSSL
+ SSL_CTX *ssl_ctx;
+#endif
+} specific_config;
+
+typedef enum { CON_STATE_CONNECT, CON_STATE_REQUEST_START, CON_STATE_READ, CON_STATE_REQUEST_END, CON_STATE_READ_POST, CON_STATE_HANDLE_REQUEST, CON_STATE_RESPONSE_START, CON_STATE_WRITE, CON_STATE_RESPONSE_END, CON_STATE_ERROR, CON_STATE_CLOSE } connection_state_t;
+
+typedef struct {
+ connection_state_t state;
+
+ /* timestamps */
+ time_t read_idle_ts;
+ time_t close_timeout_ts;
+ time_t write_request_ts;
+
+ time_t connection_start;
+ time_t request_start;
+
+ struct timeval start_tv;
+
+ size_t request_count; /* number of requests handled in this connection */
+
+ int fd; /* the FD for this connection */
+ int fde_ndx; /* index for the fdevent-handler */
+ int ndx; /* reverse mapping to server->connection[ndx] */
+
+ /* fd states */
+ int is_readable;
+ int is_writable;
+
+ int keep_alive; /* only request.c can enable it, all other just disable */
+
+ int file_started;
+ int file_finished;
+
+ chunkqueue *write_queue;
+ chunkqueue *read_queue;
+
+ int traffic_limit_reached;
+
+ off_t bytes_written; /* used by mod_accesslog, mod_rrd */
+ off_t bytes_written_cur_second; /* used by mod_accesslog, mod_rrd */
+ off_t bytes_read; /* used by mod_accesslog, mod_rrd */
+ off_t bytes_header;
+
+ int http_status;
+
+ sock_addr dst_addr;
+
+ /* request */
+ buffer *parse_request;
+ unsigned int parsed_response; /* bitfield which contains the important header-fields of the parsed response header */
+
+ request request;
+ request_uri uri;
+ physical physical;
+ response response;
+
+ size_t header_len;
+
+ buffer *authed_user;
+ array *environment; /* used to pass lighttpd internal stuff to the FastCGI/CGI apps, setenv does that */
+
+ /* response */
+ int got_response;
+
+ int in_joblist;
+
+ connection_type mode;
+
+ file_cache_entry *fce; /* filecache entry for the selected file */
+
+ void **plugin_ctx; /* plugin connection specific config */
+
+ specific_config conf; /* global connection specific config */
+
+ buffer *server_name;
+
+ /* error-handler */
+ buffer *error_handler;
+ int error_handler_saved_status;
+ int in_error_handler;
+
+ void *srv_socket; /* reference to the server-socket (typecast to server_socket) */
+
+#ifdef USE_OPENSSL
+ SSL *ssl;
+#endif
+} connection;
+
+typedef struct {
+ connection **ptr;
+ size_t size;
+ size_t used;
+} connections;
+
+
+#ifdef HAVE_IPV6
+typedef struct {
+ int family;
+ union {
+ struct in6_addr ipv6;
+ struct in_addr ipv4;
+ } addr;
+ char b2[INET6_ADDRSTRLEN + 1];
+ time_t ts;
+} inet_ntop_cache_type;
+#endif
+
+
+typedef struct {
+ buffer *uri;
+ time_t mtime;
+ int http_status;
+} realpath_cache_type;
+
+typedef struct {
+ time_t mtime; /* the key */
+ buffer *str; /* a buffer for the string represenation */
+} mtime_cache_type;
+
+typedef struct {
+ void *ptr;
+ size_t used;
+ size_t size;
+} buffer_plugin;
+
+typedef struct {
+ unsigned short port;
+ buffer *bindhost;
+ buffer *error_logfile;
+ unsigned short dont_daemonize;
+ buffer *changeroot;
+ buffer *username;
+ buffer *groupname;
+
+ buffer *errorfile_prefix;
+ buffer *license;
+ buffer *pid_file;
+
+ buffer *event_handler;
+
+ array *modules;
+
+ unsigned short max_worker;
+ unsigned short max_fds;
+
+ unsigned short log_request_header_on_error;
+ unsigned short log_state_handling;
+} server_config;
+
+typedef struct {
+ sock_addr addr;
+ int fd;
+ int fde_ndx;
+
+ buffer *ssl_pemfile;
+ unsigned short use_ipv6;
+ unsigned short is_ssl;
+ unsigned short max_request_size;
+
+ buffer *srv_token;
+
+#ifdef USE_OPENSSL
+ SSL_CTX *ssl_ctx;
+#endif
+} server_socket;
+
+typedef struct {
+ server_socket **ptr;
+
+ size_t size;
+ size_t used;
+} server_socket_array;
+
+typedef struct {
+ server_socket_array srv_sockets;
+
+ int log_error_fd;
+ int log_using_syslog;
+ fdevents *ev, *ev_ins;
+
+ buffer_plugin plugins;
+ void *plugin_slots;
+
+ int con_opened;
+ int con_read;
+ int con_written;
+ int con_closed;
+
+ int ssl_is_init;
+
+ int max_fds; /* max possible fds */
+ int cur_fds; /* currently used fds */
+ int want_fds; /* waiting fds */
+ int sockets_disabled;
+
+ /* buffers */
+ buffer *parse_full_path;
+ buffer *response_header;
+ buffer *error_log;
+ buffer *response_range;
+ buffer *tmp_buf;
+
+ buffer *tmp_chunk_len;
+
+ buffer *range_buf;
+
+ buffer *empty_string; /* is necessary for cond_match */
+
+ /* caches */
+#ifdef HAVE_IPV6
+ inet_ntop_cache_type inet_ntop_cache[INET_NTOP_CACHE_MAX];
+#endif
+ mtime_cache_type mtime_cache[FILE_CACHE_MAX];
+
+ array *split_vals;
+
+ /* Timestamps */
+ time_t cur_ts;
+ time_t last_generated_date_ts;
+ time_t last_generated_debug_ts;
+ time_t startup_ts;
+
+ buffer *ts_debug_str;
+ buffer *ts_date_str;
+
+ /* config-file */
+ array *config;
+ array *config_touched;
+
+ array *config_context;
+ specific_config **config_storage;
+
+ server_config srvconf;
+
+ int config_deprecated;
+
+ connections *conns;
+ connections *joblist;
+ connections *fdwaitqueue;
+
+ file_cache *file_cache;
+ buffer *file_cache_etag;
+ dot_stack dot_stack;
+
+ buffer_array *config_patches;
+
+ fdevent_handler_t event_handler;
+} server;
+
+
+#endif
diff --git a/src/bitset.c b/src/bitset.c
new file mode 100644
index 00000000..e40bea04
--- /dev/null
+++ b/src/bitset.c
@@ -0,0 +1,66 @@
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "bitset.h"
+#include "buffer.h"
+
+#define BITSET_BITS \
+ ( CHAR_BIT * sizeof(size_t) )
+
+#define BITSET_MASK(pos) \
+ ( ((size_t)1) << ((pos) % BITSET_BITS) )
+
+#define BITSET_WORD(set, pos) \
+ ( (set)->bits[(pos) / BITSET_BITS] )
+
+#define BITSET_USED(nbits) \
+ ( ((nbits) + (BITSET_BITS - 1)) / BITSET_BITS )
+
+bitset *bitset_init(size_t nbits) {
+ bitset *set;
+
+ set = malloc(sizeof(*set));
+ assert(set);
+
+ set->bits = calloc(BITSET_USED(nbits), sizeof(*set->bits));
+ set->nbits = nbits;
+
+ assert(set->bits);
+
+ return set;
+}
+
+void bitset_reset(bitset *set) {
+ memset(set->bits, 0, BITSET_USED(set->nbits) * sizeof(*set->bits));
+}
+
+void bitset_free(bitset *set) {
+ free(set->bits);
+ free(set);
+}
+
+void bitset_clear_bit(bitset *set, size_t pos) {
+ if (pos >= set->nbits) {
+ SEGFAULT();
+ }
+
+ BITSET_WORD(set, pos) &= ~BITSET_MASK(pos);
+}
+
+void bitset_set_bit(bitset *set, size_t pos) {
+ if (pos >= set->nbits) {
+ SEGFAULT();
+ }
+
+ BITSET_WORD(set, pos) |= BITSET_MASK(pos);
+}
+
+int bitset_test_bit(bitset *set, size_t pos) {
+ if (pos >= set->nbits) {
+ SEGFAULT();
+ }
+
+ return (BITSET_WORD(set, pos) & BITSET_MASK(pos)) != 0;
+}
diff --git a/src/bitset.h b/src/bitset.h
new file mode 100644
index 00000000..467e13fd
--- /dev/null
+++ b/src/bitset.h
@@ -0,0 +1,19 @@
+#ifndef _BITSET_H_
+#define _BITSET_H_
+
+#include <stddef.h>
+
+typedef struct {
+ size_t *bits;
+ size_t nbits;
+} bitset;
+
+bitset *bitset_init(size_t nbits);
+void bitset_reset(bitset *set);
+void bitset_free(bitset *set);
+
+void bitset_clear_bit(bitset *set, size_t pos);
+void bitset_set_bit(bitset *set, size_t pos);
+int bitset_test_bit(bitset *set, size_t pos);
+
+#endif
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 00000000..35f4c33d
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,1106 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "buffer.h"
+
+
+static const char hex_chars[] = "0123456789abcdef";
+
+
+/**
+ * init the buffer
+ *
+ */
+
+buffer* buffer_init(void) {
+ buffer *b;
+
+ b = malloc(sizeof(*b));
+ assert(b);
+
+ b->ptr = NULL;
+ b->size = 0;
+ b->used = 0;
+
+ return b;
+}
+
+/**
+ * free the buffer
+ *
+ */
+
+void buffer_free(buffer *b) {
+ if (!b) return;
+
+ if (b->size) {
+ free(b->ptr);
+ b->size = 0;
+ b->used = 0;
+ }
+ free(b);
+}
+
+void buffer_reset(buffer *b) {
+ if (!b) return;
+
+ /* limit don't reuse buffer larger than ... bytes */
+ if (b->size > BUFFER_MAX_REUSE_SIZE) {
+ free(b->ptr);
+ b->ptr = NULL;
+ b->size = 0;
+ }
+
+ b->used = 0;
+}
+
+
+/**
+ *
+ * allocate (if neccessary) enough space for 'size' bytes and
+ * set the 'used' coutner to 0
+ *
+ */
+
+#define BUFFER_PIECE_SIZE 64
+
+int buffer_prepare_copy(buffer *b, size_t size) {
+ if (!b) return -1;
+
+ if ((0 == b->size) ||
+ (size > b->size)) {
+ if (b->size) free(b->ptr);
+
+ b->size = size;
+
+ /* always allocate a multiply of BUFFER_PIECE_SIZE */
+ b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);
+
+ b->ptr = malloc(b->size);
+ assert(b->ptr);
+ }
+ b->used = 0;
+ return 0;
+}
+
+/**
+ *
+ * increase the internal buffer (if neccessary) to append another 'size' byte
+ * ->used isn't changed
+ *
+ */
+
+int buffer_prepare_append(buffer *b, size_t size) {
+ if (!b) return -1;
+
+ if (0 == b->size) {
+ b->size = size;
+
+ /* always allocate a multiply of BUFFER_PIECE_SIZE */
+ b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);
+
+ b->ptr = malloc(b->size);
+ b->used = 0;
+ assert(b->ptr);
+ } else if (b->used + size > b->size) {
+ b->size += size;
+
+ /* always allocate a multiply of BUFFER_PIECE_SIZE */
+ b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);
+
+ b->ptr = realloc(b->ptr, b->size);
+ assert(b->ptr);
+ }
+ return 0;
+}
+
+int buffer_copy_string(buffer *b, const char *s) {
+ size_t s_len;
+
+ if (!s || !b) return -1;
+
+ s_len = strlen(s);
+ if (buffer_prepare_copy(b, s_len + 1)) return -1;
+
+ memcpy(b->ptr, s, s_len + 1);
+ b->used = s_len + 1;
+
+ return 0;
+}
+
+int buffer_copy_string_len(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+#if 0
+ /* removed optimization as we have to keep the empty string
+ * in some cases for the config handling
+ *
+ * url.access-deny = ( "" )
+ */
+ if (s_len == 0) return 0;
+#endif
+ if (buffer_prepare_copy(b, s_len + 1)) return -1;
+
+ memcpy(b->ptr, s, s_len);
+ b->ptr[s_len] = '\0';
+ b->used = s_len + 1;
+
+ return 0;
+}
+
+int buffer_copy_string_buffer(buffer *b, const buffer *src) {
+ if (!src) return 0;
+
+ if (src->used == 0) {
+ b->used = 0;
+ return 0;
+ }
+ return buffer_copy_string_len(b, src->ptr, src->used - 1);
+}
+
+int buffer_append_string(buffer *b, const char *s) {
+ size_t s_len;
+
+ if (!s || !b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ return buffer_copy_string(b, s);
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ s_len = strlen(s);
+ if (buffer_prepare_append(b, s_len)) return -1;
+
+ memcpy(b->ptr + b->used - 1, s, s_len + 1);
+ b->used += s_len;
+
+ return 0;
+}
+
+int buffer_append_string_rfill(buffer *b, const char *s, size_t maxlen) {
+ size_t s_len;
+ size_t m;
+ ssize_t fill_len;
+
+ if (!s || !b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ return buffer_copy_string(b, s);
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ /* seg-fault */
+ SEGFAULT();
+ }
+
+ s_len = strlen(s);
+
+ m = s_len > maxlen + 1 ? s_len : maxlen + 1;
+
+ if (buffer_prepare_append(b, m)) return -1;
+
+ fill_len = maxlen - s_len;
+
+ if (fill_len > 0) {
+ memcpy(b->ptr + b->used - 1, s, s_len);
+ memset(b->ptr + b->used + s_len - 1, ' ', fill_len);
+ *(b->ptr + b->used + s_len + fill_len - 1) = '\0';
+ b->used += s_len + fill_len;
+ } else {
+ memcpy(b->ptr + b->used - 1, s, s_len + 1);
+ b->used += s_len;
+ }
+
+ return 0;
+}
+
+/**
+ * append a string to the end of the buffer
+ *
+ * the resulting buffer is terminated with a '\0'
+ * s is treated as a un-terminated string (a \0 is handled a normal character)
+ *
+ * @param b a buffer
+ * @param s the string
+ * @param s_len size of the string (without the terminating \0)
+ */
+
+int buffer_append_string_len(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+
+ if (s_len == 0) return 0;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ return buffer_copy_string_len(b, s, s_len);
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (buffer_prepare_append(b, s_len)) return -1;
+
+ memcpy(b->ptr + b->used - 1, s, s_len);
+ b->ptr[b->used + s_len - 1] = '\0';
+ b->used += s_len;
+
+ return 0;
+}
+
+int buffer_append_string_buffer(buffer *b, const buffer *src) {
+ if (!src) return 0;
+ if (src->used == 0) return 0;
+
+ return buffer_append_string_len(b, src->ptr, src->used - 1);
+}
+
+int buffer_append_memory(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+
+ if (s_len == 0) return 0;
+
+ if (buffer_prepare_append(b, s_len)) return -1;
+
+ memcpy(b->ptr + b->used, s, s_len);
+ b->used += s_len;
+
+ return 0;
+}
+
+int buffer_copy_memory(buffer *b, const char *s, size_t s_len) {
+ if (!s || !b) return -1;
+
+ b->used = 0;
+
+ return buffer_append_memory(b, s, s_len);
+}
+
+int buffer_append_hex(buffer *b, unsigned long value) {
+ char *buf;
+ int shift = 0;
+ unsigned long copy = value;
+
+ while (copy) {
+ copy >>= 4;
+ shift++;
+ }
+ if (shift == 0)
+ shift++;
+ if (shift & 0x01)
+ shift++;
+
+ buffer_prepare_append(b, shift + 1);
+ buf = b->ptr + b->used;
+ b->used += shift + 1;
+
+ shift <<= 2;
+ while (shift > 0) {
+ shift -= 4;
+ *(buf++) = hex_chars[(value >> shift) & 0x0F];
+ }
+ *buf = '\0';
+
+ return 0;
+}
+
+
+int ltostr(char *s, long l) {
+ int i, sign = 0;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+int buffer_copy_long(buffer *b, long l) {
+ int i, sign = 0;
+ char *s;
+
+ if (!b) return -1;
+
+ b->used = 0;
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used = i + 2;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+int buffer_append_long(buffer *b, long l) {
+ int i, sign = 0;
+ char *s;
+
+ if (!b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ SEGFAULT();
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used - 1;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used += i + 1;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+
+int buffer_copy_off_t(buffer *b, off_t l) {
+ int i, sign = 0;
+ char *s;
+
+ /* a 32bit off_t is handled by _long directly */
+ if (sizeof(l) == 4) return buffer_copy_long(b, l);
+
+ if (!b) return -1;
+
+ b->used = 0;
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used = i + 2;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+int buffer_append_off_t(buffer *b, off_t l) {
+ int i, sign = 0;
+ char *s;
+
+ /* a 32bit off_t is handled by _long directly */
+ if (sizeof(l) == 4) return buffer_append_long(b, l);
+
+ if (!b) return -1;
+
+ /* the buffer is empty, fallback to copy */
+ if (b->used == 0) {
+ SEGFAULT();
+ }
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+ if (buffer_prepare_append(b, 32)) return -1;
+
+ s = b->ptr + b->used - 1;
+
+ if (l < 0) {
+ sign = 1;
+ l = -l;
+ }
+
+ for (i = 0; l > 9; l /= 10, i++) {
+ s[i] = '0' + (l % 10);
+ }
+
+ s[i] = '0' + l;
+ if (sign) {
+ s[++i] = '-';
+ }
+ s[i+1] = '\0';
+ b->used += i + 1;
+
+ /* swap bytes again :) */
+ if (i > 0) {
+ int li = i;
+ for (; i > li / 2; i--) {
+ char c;
+
+ c = s[i];
+ s[i] = s[li - i];
+ s[li - i] = c;
+ }
+ }
+
+ return 0;
+}
+
+char int2hex(char c) {
+ return hex_chars[(c & 0x0F)];
+}
+
+/* converts hex char (0-9, A-Z, a-z) to decimal.
+ * returns 0xFF on invalid input.
+ */
+char hex2int(unsigned char hex) {
+ hex = hex - '0';
+ if (hex > 9) {
+ hex = (hex + '0' - 1) | 0x20;
+ hex = hex - 'a' + 11;
+ }
+ if (hex > 15)
+ hex = 0xFF;
+
+ return hex;
+}
+
+
+/**
+ * init the buffer
+ *
+ */
+
+buffer_array* buffer_array_init(void) {
+ buffer_array *b;
+
+ b = malloc(sizeof(*b));
+
+ assert(b);
+ b->ptr = NULL;
+ b->size = 0;
+ b->used = 0;
+
+ return b;
+}
+
+/**
+ * free the buffer_array
+ *
+ */
+
+void buffer_array_free(buffer_array *b) {
+ size_t i;
+ if (!b) return;
+
+ for (i = 0; i < b->size; i++) {
+ if (b->ptr[i]) buffer_free(b->ptr[i]);
+ }
+ free(b->ptr);
+ free(b);
+}
+
+buffer *buffer_array_append_get_buffer(buffer_array *b) {
+ size_t i;
+ if (b->size == 0) {
+ b->size = 16;
+ b->ptr = malloc(sizeof(*b->ptr) * b->size);
+ assert(b->ptr);
+ for (i = 0; i < b->size; i++) {
+ b->ptr[i] = NULL;
+ }
+ } else if (b->size == b->used) {
+ b->size += 16;
+ b->ptr = realloc(b->ptr, sizeof(*b->ptr) * b->size);
+ assert(b->ptr);
+ for (i = b->used; i < b->size; i++) {
+ b->ptr[i] = NULL;
+ }
+ }
+
+ if (b->ptr[b->used] == NULL) {
+ b->ptr[b->used] = buffer_init();
+ }
+
+ b->ptr[b->used]->used = 0;
+
+ return b->ptr[b->used++];
+}
+
+
+char * buffer_search_string_len(buffer *b, const char *needle, size_t len) {
+ size_t i;
+ if (len == 0) return NULL;
+ if (needle == NULL) return NULL;
+
+ if (b->used < len) return NULL;
+
+ for(i = 0; i < b->used - len; i++) {
+ if (0 == memcmp(b->ptr + i, needle, len)) {
+ return b->ptr + i;
+ }
+ }
+
+ return NULL;
+}
+
+buffer *buffer_init_string(const char *str) {
+ buffer *b = buffer_init();
+
+ buffer_copy_string(b, str);
+
+ return b;
+}
+
+int buffer_is_empty(buffer *b) {
+ return (b->used == 0);
+}
+
+/**
+ * check if two buffer contain the same data
+ *
+ * this is a optimized 32/64bit compare function.
+ *
+ * it is assumed that the leftmost byte have the most equality.
+ * That why the comparision is done right to left
+ *
+ */
+
+int buffer_is_equal(buffer *a, buffer *b) {
+ size_t i;
+
+ if (a->used != b->used) return 0;
+ if (a->used == 0) return 1;
+
+ for (i = a->used - 1; i < a->used && i % (sizeof(size_t)); i --) {
+ if (a->ptr[i] != b->ptr[i]) return 0;
+ }
+
+ for (i -= (sizeof(size_t)); i < a->used; i -= (sizeof(size_t))) {
+ if (*((size_t *)(a->ptr + i)) !=
+ *((size_t *)(b->ptr + i))) return 0;
+ }
+
+ return 1;
+}
+
+int buffer_is_equal_string(buffer *a, const char *s, size_t b_len) {
+ buffer b;
+
+ b.ptr = (char *)s;
+ b.used = b_len + 1;
+
+ return buffer_is_equal(a, &b);
+}
+
+/* simple-assumption:
+ *
+ * most parts are equal and doing a case conversion needs time
+ *
+ */
+int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len) {
+ size_t ndx = 0, max_ndx;
+ size_t *al, *bl;
+ size_t mask = sizeof(*al) - 1;
+
+ al = (size_t *)a;
+ bl = (size_t *)b;
+
+ /* is the alignment correct ? */
+ if ( ((size_t)al & mask) == 0 &&
+ ((size_t)bl & mask) == 0 ) {
+
+ max_ndx = ((a_len < b_len) ? a_len : b_len) & ~mask;
+
+ for (; ndx < max_ndx; ndx += sizeof(*al)) {
+ if (*al != *bl) break;
+ al++; bl++;
+
+ }
+
+ }
+
+ a = (char *)al;
+ b = (char *)bl;
+
+ max_ndx = ((a_len < b_len) ? a_len : b_len);
+
+ for (; ndx < max_ndx; ndx++) {
+ char a1 = *a++, b1 = *b++;
+
+ if (a1 != b1) {
+ if ((a1 >= 'A' && a1 <= 'Z') && (b1 >= 'a' && b1 <= 'z'))
+ a1 |= 32;
+ else if ((a1 >= 'a' && a1 <= 'z') && (b1 >= 'A' && b1 <= 'Z'))
+ b1 |= 32;
+ if ((a1 - b1) != 0) return (a1 - b1);
+
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * check if the rightmost bytes of the string are equal.
+ *
+ *
+ */
+
+int buffer_is_equal_right_len(buffer *b1, buffer *b2, size_t len) {
+ /* no, len -> equal */
+ if (len == 0) return 1;
+
+ /* len > 0, but empty buffers -> not equal */
+ if (b1->used == 0 || b2->used == 0) return 0;
+
+ /* buffers too small -> not equal */
+ if (b1->used - 1 < len || b1->used - 1 < len) return 0;
+
+ if (0 == strncmp(b1->ptr + b1->used - 1 - len,
+ b2->ptr + b2->used - 1 - len, len)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+int buffer_copy_string_hex(buffer *b, const char *in, size_t in_len) {
+ size_t i;
+
+ /* BO protection */
+ if (in_len * 2 < in_len) return -1;
+
+ buffer_prepare_copy(b, in_len * 2 + 1);
+
+ for (i = 0; i < in_len; i++) {
+ b->ptr[b->used++] = hex_chars[(in[i] >> 4) & 0x0F];
+ b->ptr[b->used++] = hex_chars[in[i] & 0x0F];
+ }
+ b->ptr[b->used++] = '\0';
+
+ return 0;
+}
+
+
+int buffer_append_string_hex(buffer *b, const char *in, size_t in_len) {
+ size_t i;
+
+ /* BO protection */
+ if (in_len * 2 < in_len) return -1;
+
+ if (b->used > 0) {
+ if (b->ptr[b->used-1] == '\0') b->used--;
+ }
+
+ buffer_prepare_append(b, in_len * 2 + 1);
+
+ for (i = 0; i < in_len; i++) {
+ b->ptr[b->used++] = hex_chars[(in[i] >> 4) & 0x0F];
+ b->ptr[b->used++] = hex_chars[in[i] & 0x0F];
+ }
+ b->ptr[b->used++] = '\0';
+
+ return 0;
+}
+
+int buffer_append_string_url_encoded(buffer *b, const char *s) {
+ unsigned char *ds, *d;
+ size_t d_len;
+
+ if (!s || !b) return -1;
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+
+ /* count to-be-encoded-characters */
+ for (ds = (unsigned char *)s, d_len = 0; *ds; ds++) {
+ if (*ds < 32 || *ds > 126) {
+ d_len += 3;
+ } else {
+ switch (*ds) {
+ case '$':
+ case '&':
+ case '+':
+ case ',':
+ case '/':
+ case ':':
+ case ';':
+ case '=':
+ case '?':
+ case '@':
+ case ' ':
+ case '#':
+ case '%':
+ case '<':
+ case '>':
+ case '"':
+ case '\'':
+ d_len += 3;
+ break;
+ default:
+ d_len ++;
+ break;
+ }
+ }
+ }
+
+ if (buffer_prepare_append(b, d_len)) return -1;
+
+ for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0; *ds; ds++) {
+ if (*ds < 32 || *ds > 126) {
+ d[d_len++] = '%';
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ } else {
+ switch (*ds) {
+ case '$':
+ case '&':
+ case '+':
+ case ',':
+ case '/':
+ case ':':
+ case ';':
+ case '=':
+ case '?':
+ case '@':
+ case ' ':
+ case '#':
+ case '%':
+ case '<':
+ case '>':
+ case '"':
+ case '\'':
+ d[d_len++] = '%';
+ d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+ d[d_len++] = hex_chars[(*ds) & 0x0F];
+ break;
+ default:
+ d[d_len++] = *ds;
+ break;
+ }
+ }
+ }
+
+ b->ptr[b->used + d_len - 1] = '\0';
+
+ b->used += d_len;
+
+ return 0;
+}
+
+int buffer_append_string_html_encoded(buffer *b, const char *s) {
+ unsigned char *ds, *d;
+ size_t d_len;
+
+ if (!s || !b) return -1;
+
+ if (b->ptr[b->used - 1] != '\0') {
+ SEGFAULT();
+ }
+
+
+ /* count to-be-encoded-characters */
+ for (ds = (unsigned char *)s, d_len = 0; *ds; ds++) {
+ switch (*ds) {
+ case '>':
+ case '<':
+ d_len += 4;
+ break;
+ case '&':
+ d_len += 5;
+ break;
+ default:
+ d_len++;
+ break;
+ }
+ }
+
+ if (buffer_prepare_append(b, d_len)) return -1;
+
+ for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0; *ds; ds++) {
+ switch (*ds) {
+ case '>':
+ d[d_len++] = '&';
+ d[d_len++] = 'g';
+ d[d_len++] = 't';
+ d[d_len++] = ';';
+
+ break;
+ case '<':
+ d[d_len++] = '&';
+ d[d_len++] = 'l';
+ d[d_len++] = 't';
+ d[d_len++] = ';';
+
+ break;
+ case '&':
+ d[d_len++] = '&';
+ d[d_len++] = 'a';
+ d[d_len++] = 'm';
+ d[d_len++] = 'p';
+ d[d_len++] = ';';
+
+ break;
+
+ default:
+ d[d_len++] = *ds;
+ break;
+ }
+ }
+
+ b->ptr[b->used + d_len - 1] = '\0';
+ b->used += d_len;
+
+ return 0;
+}
+
+/* decodes url-special-chars inplace.
+ * ignores %00 (null-byte).
+ */
+int buffer_urldecode(buffer *url) {
+ unsigned char high, low;
+ const char *src;
+ char *dst;
+
+ if (!url || !url->ptr) return -1;
+
+ src = (const char*) url->ptr;
+ dst = (char*) url->ptr;
+
+ while ((*src) != '\0') {
+#if 1
+ if (*src == '+') {
+ *dst = ' ';
+ } else
+#endif
+ if (*src == '%') {
+ *dst = '%';
+
+ high = hex2int(*(src + 1));
+ if (high != 0xFF) {
+ low = hex2int(*(src + 2));
+ if (low != 0xFF) {
+ high = (high << 4) | low;
+
+ /* map control-characters out */
+ if (high < 32 || high == 127) high = '_';
+
+ *dst = high;
+ src += 2;
+ }
+ }
+ } else {
+ *dst = *src;
+ }
+
+ dst++;
+ src++;
+ }
+
+ *dst = '\0';
+ url->used = (dst - url->ptr) + 1;
+
+ return 0;
+}
+
+int buffer_path_simplify(dot_stack *stack, buffer *out, buffer *in) {
+ char *last_slash, *slash;
+ size_t i;
+
+ /*
+ * /./ -> /
+ * ^/../ -> /
+ * /abc/../ -> /
+ */
+
+ stack->used = 0;
+
+ for (last_slash = in->ptr; NULL != (slash = strchr(last_slash, '/')); last_slash = slash + 1) {
+ int n;
+
+ n = slash - last_slash;
+
+ if ((n == 0) || /* // */
+ (n == 1 && *last_slash == '.') || /* /./ */
+ (n == 2 && *last_slash == '.' && *(last_slash+1) == '.')) /* /../ */ {
+ if (n == 2 && stack->used > 0) stack->used--;
+ } else {
+ if (stack->size == 0) {
+ stack->size = 16;
+ stack->ptr = malloc(stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ stack->used = 0;
+ for (i = 0; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ } else if (stack->size == stack->used) {
+ stack->size += 16;
+ stack->ptr = realloc(stack->ptr, stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ for (i = stack->used; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ }
+
+ stack->ptr[stack->used]->start = last_slash;
+ stack->ptr[stack->used]->len = n + 1;
+
+ stack->used++;
+ }
+ }
+
+ if (stack->size == 0) {
+ stack->size = 16;
+ stack->ptr = malloc(stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ stack->used = 0;
+ for (i = 0; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ } else if (stack->size == stack->used) {
+ stack->size += 16;
+ stack->ptr = realloc(stack->ptr, stack->size * sizeof(*stack->ptr));
+ assert(stack->ptr);
+
+ for (i = stack->used; i < stack->size; i++) {
+ stack->ptr[i] = malloc(sizeof(**stack->ptr));
+ assert(stack->ptr[i]);
+ }
+ }
+
+ stack->ptr[stack->used]->start = last_slash;
+ stack->ptr[stack->used]->len = in->used - (last_slash - in->ptr) - 1;
+
+ stack->used++;
+
+ BUFFER_COPY_STRING_CONST(out, "/");
+
+ for (i = 0; i < stack->used; i++) {
+ buffer_append_string_len(out, stack->ptr[i]->start, stack->ptr[i]->len);
+ }
+
+ return 0;
+}
+
+inline int light_isdigit(int c) {
+ return (c >= '0' && c <= '9');
+}
+
+inline int light_isxdigit(int c) {
+ if (light_isdigit(c)) return 1;
+
+ c |= 32;
+ return (c >= 'a' && c <= 'f');
+}
+
+inline int light_isalpha(int c) {
+ c |= 32;
+ return (c >= 'a' && c <= 'z');
+}
+
+inline int light_isalnum(int c) {
+ return light_isdigit(c) || light_isalpha(c);
+}
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644
index 00000000..53febed6
--- /dev/null
+++ b/src/buffer.h
@@ -0,0 +1,122 @@
+#ifndef _BUFFER_H_
+#define _BUFFER_H_
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "settings.h"
+
+typedef struct {
+ char *ptr;
+
+ size_t used;
+ size_t size;
+} buffer;
+
+typedef struct {
+ buffer **ptr;
+
+ size_t used;
+ size_t size;
+} buffer_array;
+
+typedef struct {
+ char *ptr;
+
+ size_t offset; /* input-pointer */
+
+ size_t used; /* output-pointer */
+ size_t size;
+} read_buffer;
+
+typedef struct {
+ const char *start;
+ size_t len;
+} dot;
+
+typedef struct {
+ dot **ptr;
+ size_t used;
+ size_t size;
+} dot_stack;
+
+buffer_array* buffer_array_init(void);
+void buffer_array_free(buffer_array *b);
+buffer *buffer_array_append_get_buffer(buffer_array *b);
+
+buffer* buffer_init(void);
+buffer* buffer_init_string(const char *str);
+void buffer_free(buffer *b);
+void buffer_reset(buffer *b);
+
+int buffer_prepare_copy(buffer *b, size_t size);
+int buffer_prepare_append(buffer *b, size_t size);
+
+int buffer_copy_string(buffer *b, const char *s);
+int buffer_copy_string_len(buffer *b, const char *s, size_t s_len);
+int buffer_copy_string_buffer(buffer *b, const buffer *src);
+int buffer_copy_string_hex(buffer *b, const char *in, size_t in_len);
+
+int buffer_copy_long(buffer *b, long l);
+int buffer_copy_off_t(buffer *b, off_t l);
+
+int buffer_copy_memory(buffer *b, const char *s, size_t s_len);
+
+int buffer_append_string(buffer *b, const char *s);
+int buffer_append_string_len(buffer *b, const char *s, size_t s_len);
+int buffer_append_string_buffer(buffer *b, const buffer *src);
+int buffer_append_string_lfill(buffer *b, const char *s, size_t maxlen);
+int buffer_append_string_rfill(buffer *b, const char *s, size_t maxlen);
+
+int buffer_append_hex(buffer *b, unsigned long len);
+int buffer_append_long(buffer *b, long l);
+int buffer_append_off_t(buffer *b, off_t l);
+
+int buffer_append_memory(buffer *b, const char *s, size_t s_len);
+
+char * buffer_search_string_len(buffer *b, const char *needle, size_t len);
+
+int buffer_is_empty(buffer *b);
+int buffer_is_equal(buffer *a, buffer *b);
+int buffer_is_equal_right_len(buffer *a, buffer *b, size_t len);
+int buffer_is_equal_string(buffer *a, const char *s, size_t b_len);
+int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len);
+
+int buffer_append_string_hex(buffer *b, const char *in, size_t in_len);
+int buffer_append_string_url_encoded(buffer *b, const char *s);
+int buffer_append_string_html_encoded(buffer *b, const char *s);
+
+int buffer_urldecode(buffer *url);
+int buffer_path_simplify(dot_stack *stack, buffer *out, buffer *in);
+
+/** deprecated */
+int ltostr(char *s, long l);
+char hex2int(unsigned char c);
+char int2hex(char i);
+
+int light_isdigit(int c);
+int light_isxdigit(int c);
+int light_isalpha(int c);
+int light_isalnum(int c);
+
+#define BUFFER_APPEND_STRING_CONST(x, y) \
+ buffer_append_string_len(x, y, sizeof(y) - 1)
+
+#define BUFFER_COPY_STRING_CONST(x, y) \
+ buffer_copy_string_len(x, y, sizeof(y) - 1)
+
+#define BUFFER_APPEND_SLASH(x) \
+ if (x->used > 1 && x->ptr[x->used - 2] != '/') { BUFFER_APPEND_STRING_CONST(x, "/"); }
+
+#define CONST_STR_LEN(x) x, sizeof(x) - 1
+#define CONST_BUF_LEN(x) x->ptr, x->used - 1
+
+
+#define SEGFAULT() abort()
+#define UNUSED(x) ( (void)(x) )
+
+#endif
diff --git a/src/chunk.c b/src/chunk.c
new file mode 100644
index 00000000..ee631c60
--- /dev/null
+++ b/src/chunk.c
@@ -0,0 +1,312 @@
+/**
+ * the network chunk-API
+ *
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "chunk.h"
+
+chunkqueue *chunkqueue_init(void) {
+ chunkqueue *cq;
+
+ cq = calloc(1, sizeof(*cq));
+
+ cq->first = NULL;
+ cq->last = NULL;
+
+ cq->unused = NULL;
+
+ return cq;
+}
+
+static chunk *chunk_init(void) {
+ chunk *c;
+
+ c = calloc(1, sizeof(*c));
+
+ /* c->mem overlaps with c->data.file.name */
+ c->data.mem = buffer_init();
+ c->next = NULL;
+
+ return c;
+}
+
+static void chunk_free(chunk *c) {
+ if (!c) return;
+
+ /* c->data.mem overlaps with c->data.file.name */
+ switch (c->type) {
+ case MEM_CHUNK: buffer_free(c->data.mem); break;
+ case FILE_CHUNK: buffer_free(c->data.file.name); break;
+ default: break;
+ }
+
+ free(c);
+}
+
+void chunkqueue_free(chunkqueue *cq) {
+ chunk *c, *pc;
+
+ if (!cq) return;
+
+ for (c = cq->first; c; ) {
+ pc = c;
+ c = c->next;
+ chunk_free(pc);
+ }
+
+ for (c = cq->unused; c; ) {
+ pc = c;
+ c = c->next;
+ chunk_free(pc);
+ }
+
+ free(cq);
+}
+
+static chunk *chunkqueue_get_unused_chunk(chunkqueue *cq) {
+ chunk *c;
+
+ /* check if we have a unused chunk */
+ if (!cq->unused) {
+ c = chunk_init();
+ } else {
+ /* take the first element from the list (a stack) */
+ c = cq->unused;
+ cq->unused = c->next;
+ c->next = NULL;
+ }
+
+ return c;
+}
+
+static int chunkqueue_prepend_chunk(chunkqueue *cq, chunk *c) {
+ c->next = cq->first;
+ cq->first = c;
+
+ if (cq->last == NULL) {
+ cq->last = c;
+ }
+
+ return 0;
+}
+
+static int chunkqueue_append_chunk(chunkqueue *cq, chunk *c) {
+ if (cq->last) {
+ cq->last->next = c;
+ }
+ cq->last = c;
+
+ if (cq->first == NULL) {
+ cq->first = c;
+ }
+
+ return 0;
+}
+
+void chunkqueue_reset(chunkqueue *cq) {
+ /* move everything to the unused queue */
+
+ if (cq->last == NULL) return;
+
+ cq->last->next = cq->unused;
+ cq->unused = cq->first;
+
+ /* disconnect active chain */
+ cq->first = cq->last = NULL;
+}
+
+int chunkqueue_append_file(chunkqueue *cq, buffer *fn, off_t offset, off_t len) {
+ chunk *c;
+
+ if (len == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = FILE_CHUNK;
+
+ buffer_copy_string_buffer(c->data.file.name, fn);
+ c->data.file.offset = offset;
+ c->data.file.length = len;
+ c->offset = 0;
+
+ chunkqueue_append_chunk(cq, c);
+
+ return 0;
+}
+
+int chunkqueue_append_buffer(chunkqueue *cq, buffer *mem) {
+ chunk *c;
+
+ if (mem->used == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_copy_string_buffer(c->data.mem, mem);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return 0;
+}
+
+int chunkqueue_prepend_buffer(chunkqueue *cq, buffer *mem) {
+ chunk *c;
+
+ if (mem->used == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_copy_string_buffer(c->data.mem, mem);
+
+ chunkqueue_prepend_chunk(cq, c);
+
+ return 0;
+}
+
+int chunkqueue_append_mem(chunkqueue *cq, const char * mem, size_t len) {
+ chunk *c;
+
+ if (len == 0) return 0;
+
+ c = chunkqueue_get_unused_chunk(cq);
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_copy_string_len(c->data.mem, mem, len - 1);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return 0;
+}
+
+buffer * chunkqueue_get_prepend_buffer(chunkqueue *cq) {
+ chunk *c;
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_reset(c->data.mem);
+
+ chunkqueue_prepend_chunk(cq, c);
+
+ return c->data.mem;
+}
+
+buffer *chunkqueue_get_append_buffer(chunkqueue *cq) {
+ chunk *c;
+
+ c = chunkqueue_get_unused_chunk(cq);
+
+ c->type = MEM_CHUNK;
+ c->offset = 0;
+ buffer_reset(c->data.mem);
+
+ chunkqueue_append_chunk(cq, c);
+
+ return c->data.mem;
+}
+
+off_t chunkqueue_length(chunkqueue *cq) {
+ off_t len = 0;
+ chunk *c;
+
+ for (c = cq->first; c; c = c->next) {
+ switch (c->type) {
+ case MEM_CHUNK:
+ len += c->data.mem->used ? c->data.mem->used - 1 : 0;
+ break;
+ case FILE_CHUNK:
+ len += c->data.file.length;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return len;
+}
+
+off_t chunkqueue_written(chunkqueue *cq) {
+ off_t len = 0;
+ chunk *c;
+
+ for (c = cq->first; c; c = c->next) {
+ switch (c->type) {
+ case MEM_CHUNK:
+ case FILE_CHUNK:
+ len += c->offset;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return len;
+}
+
+int chunkqueue_is_empty(chunkqueue *cq) {
+ return cq->first ? 0 : 1;
+}
+
+#ifdef DEBUG_CHUNK
+
+static int write_chunkqueue(int fd, chunkqueue *c) {
+ UNUSED(fd);
+ UNUSED(c);
+
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ chunkqueue *c;
+ buffer *b, *fn;
+
+ UNUSED(argc);
+ UNUSED(argv);
+
+ c = chunkqueue_init();
+
+ fn = buffer_init_string("server.c");
+
+ chunkqueue_append_file(c, fn, 0, 10);
+ chunkqueue_append_file(c, fn, 10, 10);
+ chunkqueue_append_file(c, fn, 20, 10);
+
+ write_chunkqueue(STDERR_FILENO, c);
+ chunkqueue_reset(c);
+
+ b = buffer_init();
+ buffer_copy_string(b, "\ntest string mit vielen Zeichen\n");
+ chunkqueue_append_buffer(c, b);
+
+ write_chunkqueue(STDERR_FILENO, c);
+ chunkqueue_reset(c);
+
+ chunkqueue_append_file(c, fn, 0, 10);
+ buffer_copy_string(b, "\ntest string mit vielen Zeichen\n");
+ chunkqueue_append_buffer(c, b);
+ chunkqueue_append_file(c, fn, 10, 10);
+ chunkqueue_append_file(c, fn, 20, 10);
+ chunkqueue_append_file(c, fn, 50, 40);
+
+ write_chunkqueue(STDERR_FILENO, c);
+ chunkqueue_reset(c);
+
+ chunkqueue_free(c);
+
+ return 0;
+}
+#endif
diff --git a/src/chunk.h b/src/chunk.h
new file mode 100644
index 00000000..9d5ed25a
--- /dev/null
+++ b/src/chunk.h
@@ -0,0 +1,56 @@
+#ifndef _CHUNK_H_
+#define _CHUNK_H_
+
+#include "buffer.h"
+
+typedef struct chunk {
+ /* ok, this one is tricky:
+ *
+ * MEM_CHUNK
+ * b: the chunk it self
+ * FILE_CHUNK
+ * b: a buffer for the filename
+ */
+
+ enum { UNUSED_CHUNK, MEM_CHUNK, FILE_CHUNK } type;
+
+ union {
+ buffer *mem;
+ struct {
+ buffer *name;
+ off_t offset;
+ off_t length;
+ } file;
+ } data;
+
+ /* how many bytes are already handled */
+
+ off_t offset;
+
+ struct chunk *next;
+} chunk;
+
+typedef struct {
+ chunk *first;
+ chunk *last;
+
+ chunk *unused;
+} chunkqueue;
+
+chunkqueue *chunkqueue_init(void);
+int chunkqueue_append_file(chunkqueue *c, buffer *fn, off_t offset, off_t len);
+int chunkqueue_append_mem(chunkqueue *c, const char *mem, size_t len);
+int chunkqueue_append_buffer(chunkqueue *c, buffer *mem);
+int chunkqueue_prepend_buffer(chunkqueue *c, buffer *mem);
+
+buffer * chunkqueue_get_append_buffer(chunkqueue *c);
+buffer * chunkqueue_get_prepend_buffer(chunkqueue *c);
+
+off_t chunkqueue_length(chunkqueue *c);
+off_t chunkqueue_written(chunkqueue *c);
+void chunkqueue_free(chunkqueue *c);
+void chunkqueue_reset(chunkqueue *c);
+
+int chunkqueue_is_empty(chunkqueue *c);
+
+#endif
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 00000000..27feb6ee
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,1025 @@
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "server.h"
+#include "log.h"
+#include "stream.h"
+#include "plugin.h"
+#ifdef USE_LICENSE
+#include "license.h"
+#endif
+
+#include "configparser.h"
+#include "configfile.h"
+
+/* handle global options */
+
+int config_insert_values_global(server *srv, array *ca, const config_values_t cv[]) {
+ size_t i;
+ data_unset *du;
+
+ for (i = 0; cv[i].key; i++) {
+ data_string *touched;
+
+ if (NULL == (du = array_get_element(ca, cv[i].key))) {
+ /* no found */
+
+ continue;
+ }
+
+ /* touched */
+ touched = data_string_init();
+
+ buffer_copy_string(touched->value, "");
+ buffer_copy_string_buffer(touched->key, du->key);
+
+ array_insert_unique(srv->config_touched, (data_unset *)touched);
+ }
+
+ return config_insert_values_internal(srv, ca, cv);
+}
+
+/* parse config array */
+
+int config_insert_values_internal(server *srv, array *ca, const config_values_t cv[]) {
+ size_t i;
+ data_unset *du;
+
+ for (i = 0; cv[i].key; i++) {
+
+ if (NULL == (du = array_get_element(ca, cv[i].key))) {
+ /* no found */
+
+ continue;
+ }
+
+ switch (cv[i].type) {
+ case T_CONFIG_ARRAY:
+ if (du->type == TYPE_ARRAY) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type == TYPE_STRING) {
+ data_string *ds = data_string_init();
+
+ buffer_copy_string_buffer(ds->value, ((data_string *)(da->value->data[j]))->value);
+ buffer_copy_string_buffer(ds->key, ((data_string *)(da->value->data[j]))->key);
+
+ array_insert_unique(cv[i].destination, (data_unset *)ds);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", cv[i].key, "[", da->value->data[i]->key, "](string)");
+
+ return -1;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss", "unexpected type for key: ", cv[i].key, "array of strings");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_STRING:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ buffer_copy_string_buffer(cv[i].destination, ds->value);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "unexpected type for key: ", cv[i].key, "(string)", "\"...\"");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_SHORT:
+ switch(du->type) {
+ case TYPE_INTEGER: {
+ data_integer *di = (data_integer *)du;
+
+ *((unsigned short *)(cv[i].destination)) = di->value;
+ break;
+ }
+ case TYPE_STRING: {
+ data_string *ds = (data_string *)du;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbss", "unexpected type for key: ", cv[i].key, ds->value, "(short)", "0 ... 65535");
+
+ return -1;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssdss", "unexpected type for key: ", cv[i].key, du->type, "(short)", "0 ... 65535");
+ return -1;
+ }
+ break;
+ case T_CONFIG_BOOLEAN:
+ if (du->type == TYPE_STRING) {
+ data_string *ds = (data_string *)du;
+
+ if (0 == strcmp(ds->value->ptr, "enable")) {
+ *((unsigned short *)(cv[i].destination)) = 1;
+ } else if (0 == strcmp(ds->value->ptr, "disable")) {
+ *((unsigned short *)(cv[i].destination)) = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs", "ERROR: unexpected value for key:", cv[i].key, ds->value, "(enable|disable)");
+
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: unexpected type for key:", cv[i].key, "(string)", "\"(enable|disable)\"");
+
+ return -1;
+ }
+ break;
+ case T_CONFIG_LOCAL:
+ case T_CONFIG_UNSET:
+ break;
+ case T_CONFIG_DEPRECATED:
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: found deprecated key:", cv[i].key, "-", (char *)(cv[i].destination));
+
+ srv->config_deprecated = 1;
+
+ break;
+ }
+ }
+ return 0;
+}
+
+static int config_insert(server *srv) {
+ size_t i;
+ int ret = 0;
+
+ config_values_t cv[] = {
+ { "server.bind", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 0 */
+ { "server.errorlog", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 1 */
+ { "server.errorfile-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 2 */
+ { "server.chroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 3 */
+ { "server.username", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 4 */
+ { "server.groupname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 5 */
+ { "server.port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 6 */
+ { "server.tag", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "server.use-ipv6", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "server.modules", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_SERVER }, /* 9 */
+
+ { "server.event-handler", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 10 */
+ { "server.pid-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 11 */
+ { "server.max-request-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
+ { "server.max-worker", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 13 */
+ { "server.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
+ { "server.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */
+ { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 16 */
+ { "server.max-keep-alive-requests", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */
+ { "server.name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 18 */
+ { "server.max-keep-alive-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 19 */
+
+ { "server.max-read-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 20 */
+ { "server.max-write-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 21 */
+ { "server.error-handler-404", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 22 */
+ { "server.max-fds", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 23 */
+ { "server.follow-symlink", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 24 */
+ { "server.kbytes-per-second", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 25 */
+ { "connection.kbytes-per-second", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 26 */
+ { "mimetype.use-xattr", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 27 */
+ { "mimetype.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 28 */
+ { "ssl.pemfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 29 */
+
+ { "ssl.engine", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 30 */
+
+ { "debug.log-file-not-found", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 31 */
+ { "debug.log-request-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 32 */
+ { "debug.log-response-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 33 */
+ { "debug.log-request-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 34 */
+
+ { "server.protocol-http11", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 35 */
+ { "debug.log-request-header-on-error", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 36 */
+ { "debug.log-state-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 37 */
+
+
+ { "server.host", "use server.bind instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.docroot", "use server.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.virtual-root", "load mod_simple_vhost and use simple-vhost.server-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.virtual-default-host", "load mod_simple_vhost and use simple-vhost.default-host instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.virtual-docroot", "load mod_simple_vhost and use simple-vhost.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.userid", "use server.username instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.groupid", "use server.groupname instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+ { "server.use-keep-alive", "use server.max-keep-alive-requests = 0 instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET },
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+
+ /* 0 */
+ cv[0].destination = srv->srvconf.bindhost;
+ cv[1].destination = srv->srvconf.error_logfile;
+ cv[2].destination = srv->srvconf.errorfile_prefix;
+ cv[3].destination = srv->srvconf.changeroot;
+ cv[4].destination = srv->srvconf.username;
+ cv[5].destination = srv->srvconf.groupname;
+ cv[6].destination = &(srv->srvconf.port);
+
+ cv[9].destination = srv->srvconf.modules;
+ cv[10].destination = srv->srvconf.event_handler;
+ cv[11].destination = srv->srvconf.pid_file;
+
+ cv[13].destination = &(srv->srvconf.max_worker);
+ cv[23].destination = &(srv->srvconf.max_fds);
+ cv[36].destination = &(srv->srvconf.log_request_header_on_error);
+ cv[37].destination = &(srv->srvconf.log_state_handling);
+
+ srv->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ assert(srv->config_storage);
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ specific_config *s;
+
+ s = calloc(1, sizeof(specific_config));
+ assert(s);
+ s->document_root = buffer_init();
+ s->dir_listing = 0;
+ s->indexfiles = array_init();
+ s->mimetypes = array_init();
+ s->server_name = buffer_init();
+ s->ssl_pemfile = buffer_init();
+ s->error_handler = buffer_init();
+ s->server_tag = buffer_init();
+ s->max_keep_alive_requests = 128;
+ s->max_keep_alive_idle = 30;
+ s->max_read_idle = 60;
+ s->max_write_idle = 360;
+ s->use_xattr = 0;
+ s->is_ssl = 0;
+ s->use_ipv6 = 0;
+ s->follow_symlink = 1;
+ s->kbytes_per_second = 0;
+ s->allow_http11 = 1;
+ s->global_kbytes_per_second = 0;
+ s->global_bytes_per_second_cnt = 0;
+ s->global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt;
+
+ cv[7].destination = s->server_tag;
+ cv[8].destination = &(s->use_ipv6);
+
+
+ cv[12].destination = &(s->max_request_size);
+ /* 13 max-worker */
+ cv[14].destination = s->document_root;
+ cv[15].destination = &(s->dir_listing);
+ cv[16].destination = s->indexfiles;
+ cv[17].destination = &(s->max_keep_alive_requests);
+ cv[18].destination = s->server_name;
+ cv[19].destination = &(s->max_keep_alive_idle);
+ cv[20].destination = &(s->max_read_idle);
+ cv[21].destination = &(s->max_write_idle);
+ cv[22].destination = s->error_handler;
+ cv[24].destination = &(s->follow_symlink);
+ /* 23 -> max-fds */
+ cv[25].destination = &(s->global_kbytes_per_second);
+ cv[26].destination = &(s->kbytes_per_second);
+ cv[27].destination = &(s->use_xattr);
+ cv[28].destination = s->mimetypes;
+ cv[29].destination = s->ssl_pemfile;
+ cv[30].destination = &(s->is_ssl);
+
+ cv[31].destination = &(s->log_file_not_found);
+ cv[32].destination = &(s->log_request_handling);
+ cv[33].destination = &(s->log_response_header);
+ cv[34].destination = &(s->log_request_header);
+
+ cv[35].destination = &(s->allow_http11);
+
+ srv->config_storage[i] = s;
+
+ if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) {
+ break;
+ }
+ }
+
+ return ret;
+
+}
+
+int config_check_cond(server *srv, connection *con, data_config *dc) {
+ buffer *l;
+ server_socket *srv_sock = con->srv_socket;
+ /* pass the rules */
+
+ l = srv->empty_string;
+
+ if (0 == strcmp(dc->comp_key->ptr, "HTTPhost")) {
+ l = con->uri.authority;
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPurl")) {
+ l = con->uri.path;
+ } else if (0 == strcmp(dc->comp_key->ptr, "SERVERsocket")) {
+ l = srv_sock->srv_token;
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPreferer")) {
+ data_string *ds;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Referer"))) {
+ l = ds->value;
+ }
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPcookie")) {
+ data_string *ds;
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) {
+ l = ds->value;
+ }
+ } else if (0 == strcmp(dc->comp_key->ptr, "HTTPuseragent")) {
+ data_string *ds;
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "User-Agent"))) {
+ l = ds->value;
+ }
+ } else {
+ return 0;
+ }
+
+ switch(dc->cond) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ if (buffer_is_equal(l, dc->match.string)) {
+ return (dc->cond == CONFIG_COND_EQ) ? 1 : 0;
+ } else {
+ return (dc->cond == CONFIG_COND_EQ) ? 0 : 1;
+ }
+ break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+#define N 10
+ int ovec[N * 3];
+ int n;
+
+ n = pcre_exec(dc->match.regex, NULL, l->ptr, l->used - 1, 0, 0, ovec, N * 3);
+
+ if (n > 0) {
+ return (dc->cond == CONFIG_COND_MATCH) ? 1 : 0;
+ } else {
+ return (dc->cond == CONFIG_COND_MATCH) ? 0 : 1;
+ }
+
+ break;
+ }
+#endif
+ default:
+ /* no way */
+ break;
+ }
+
+ return 0;
+}
+
+#define PATCH(x) con->conf.x = s->x
+int config_setup_connection(server *srv, connection *con) {
+ specific_config *s = srv->config_storage[0];
+
+ PATCH(allow_http11);
+ PATCH(mimetypes);
+ PATCH(document_root);
+ PATCH(dir_listing);
+ PATCH(indexfiles);
+ PATCH(max_keep_alive_requests);
+ PATCH(max_keep_alive_idle);
+ PATCH(max_read_idle);
+ PATCH(max_write_idle);
+ PATCH(use_xattr);
+ PATCH(error_handler);
+ PATCH(follow_symlink);
+ PATCH(server_tag);
+ PATCH(kbytes_per_second);
+ PATCH(global_kbytes_per_second);
+ PATCH(global_bytes_per_second_cnt);
+ con->conf.global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt;
+ buffer_copy_string_buffer(con->server_name, s->server_name);
+
+ PATCH(log_request_header);
+ PATCH(log_response_header);
+ PATCH(log_request_handling);
+ PATCH(log_file_not_found);
+
+ return 0;
+}
+
+int config_patch_connection(server *srv, connection *con, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ specific_config *s = srv->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.document-root"))) {
+ PATCH(document_root);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.dir-listing"))) {
+ PATCH(dir_listing);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.error-handler-404"))) {
+ PATCH(error_handler);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.indexfiles"))) {
+ PATCH(indexfiles);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.assign"))) {
+ PATCH(mimetypes);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-requests"))) {
+ PATCH(max_keep_alive_requests);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-idle"))) {
+ PATCH(max_keep_alive_idle);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-write-idle"))) {
+ PATCH(max_write_idle);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-read-idle"))) {
+ PATCH(max_read_idle);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.use-xattr"))) {
+ PATCH(use_xattr);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.pemfile"))) {
+ PATCH(ssl_pemfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.engine"))) {
+ PATCH(is_ssl);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.follow-symlink"))) {
+ PATCH(follow_symlink);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.name"))) {
+ buffer_copy_string_buffer(con->server_name, s->server_name);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.tag"))) {
+ PATCH(server_tag);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("connection.kbytes-per-second"))) {
+ PATCH(kbytes_per_second);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-request-handling"))) {
+ PATCH(log_request_handling);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-request-header"))) {
+ PATCH(log_request_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-response-header"))) {
+ PATCH(log_response_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-file-not-found"))) {
+ PATCH(log_file_not_found);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.protocol-http11"))) {
+ PATCH(allow_http11);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.kbytes-per-second"))) {
+ PATCH(global_kbytes_per_second);
+ PATCH(global_bytes_per_second_cnt);
+ con->conf.global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt;
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+typedef struct {
+ int foo;
+ int bar;
+
+ char *input;
+ size_t offset;
+ size_t size;
+
+ int line_pos;
+ int line;
+
+ int in_key;
+ int in_brace;
+ int in_cond;
+} tokenizer_t;
+
+static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer *token) {
+ int tid = 0;
+ size_t i;
+
+ for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) {
+ char c = t->input[t->offset];
+ char *start = NULL;
+
+ switch (c) {
+ case '=':
+ if (t->in_brace) {
+ if (t->input[t->offset + 1] == '>') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "=>");
+
+ tid = TK_ARRAY_ASSIGN;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "use => for assignments in arrays");
+ return -1;
+ }
+ } else if (t->in_cond) {
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "==");
+
+ tid = TK_EQ;
+ } else if (t->input[t->offset + 1] == '~') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "=~");
+
+ tid = TK_MATCH;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "only =~ and == are allow in the condition");
+ return -1;
+ }
+ } else if (t->in_key) {
+ tid = TK_ASSIGN;
+
+ buffer_copy_string_len(token, t->input + t->offset, 1);
+
+ t->offset++;
+ t->line_pos++;
+ t->in_key = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected equal-sign: =");
+ return -1;
+ }
+
+ break;
+ case '!':
+ if (t->in_cond) {
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "!=");
+
+ tid = TK_NE;
+ } else if (t->input[t->offset + 1] == '~') {
+ t->offset += 2;
+
+ buffer_copy_string(token, "!~");
+
+ tid = TK_NOMATCH;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "only !~ and != are allow in the condition");
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected exclamation-marks: !");
+ return -1;
+ }
+
+ break;
+ case '\t':
+ case ' ':
+ t->offset++;
+ t->line_pos++;
+ break;
+ case '\r':
+ if (t->in_brace == 0) {
+ if (t->input[t->offset + 1] == '\n') {
+ t->in_key = 1;
+ t->offset += 2;
+
+ tid = TK_EOL;
+ t->line++;
+ t->line_pos = 1;
+
+ buffer_copy_string(token, "(EOL)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "CR without LF");
+ return 0;
+ }
+ } else {
+ t->offset++;
+ t->line_pos++;
+ }
+ break;
+ case '\n':
+ if (t->in_brace == 0) {
+ t->in_key = 1;
+
+ tid = TK_EOL;
+
+ buffer_copy_string(token, "(EOL)");
+ }
+ t->line++;
+ t->line_pos = 1;
+ t->offset++;
+ break;
+ case ',':
+ if (t->in_brace > 0) {
+ tid = TK_COMMA;
+
+ buffer_copy_string(token, "(COMMA)");
+ }
+
+ t->offset++;
+ t->line_pos++;
+ break;
+ case '"':
+ /* search for the terminating " */
+ start = t->input + t->offset + 1;
+ buffer_copy_string(token, "");
+
+ for (i = 1; t->input[t->offset + i]; i++) {
+ if (t->input[t->offset + i] == '\\' &&
+ t->input[t->offset + i + 1] == '"') {
+
+ buffer_append_string_len(token, start, t->input + t->offset + i - start);
+
+ start = t->input + t->offset + i + 1;
+
+ /* skip the " */
+ i++;
+ continue;
+ }
+
+
+ if (t->input[t->offset + i] == '"') {
+ tid = TK_STRING;
+
+ buffer_append_string_len(token, start, t->input + t->offset + i - start);
+
+ break;
+ }
+ }
+
+ if (t->input[t->offset + i] == '\0') {
+ /* ERROR */
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "missing closing quote");
+
+ return -1;
+ }
+
+ t->offset += i + 1;
+ t->line_pos += i + 1;
+
+ break;
+ case '(':
+ t->offset++;
+ t->in_brace++;
+
+ tid = TK_LPARAN;
+
+ buffer_copy_string(token, "(");
+ break;
+ case ')':
+ t->offset++;
+ t->in_brace--;
+
+ tid = TK_RPARAN;
+
+ buffer_copy_string(token, ")");
+ break;
+ case '$':
+ t->offset++;
+
+ tid = TK_DOLLAR;
+ t->in_cond = 1;
+ t->in_key = 0;
+
+ buffer_copy_string(token, "$");
+
+ break;
+ case '{':
+ t->offset++;
+
+ tid = TK_LCURLY;
+ t->in_key = 1;
+ t->in_cond = 0;
+
+ buffer_copy_string(token, "{");
+
+ break;
+
+ case '}':
+ t->offset++;
+
+ tid = TK_RCURLY;
+
+ buffer_copy_string(token, "}");
+
+ break;
+ case '[':
+ t->offset++;
+
+ tid = TK_LBRACKET;
+
+ buffer_copy_string(token, "[");
+
+ break;
+
+ case ']':
+ t->offset++;
+
+ tid = TK_RBRACKET;
+
+ buffer_copy_string(token, "]");
+
+ break;
+ case '#':
+ for (i = 1; t->input[t->offset + i] &&
+ (t->input[t->offset + i] != '\n' && t->input[t->offset + i] != '\r');
+ i++);
+
+ t->offset += i;
+
+ break;
+ default:
+ if (t->in_key) {
+ /* the key might consist of [-.0-9a-z] */
+ for (i = 0; t->input[t->offset + i] &&
+ (isalnum((unsigned char)t->input[t->offset + i]) ||
+ t->input[t->offset + i] == '.' ||
+ t->input[t->offset + i] == '-'
+ ); i++);
+
+ if (i && t->input[t->offset + i]) {
+ tid = TK_LKEY;
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid character in lvalue");
+ return -1;
+ }
+ } else if (t->in_cond) {
+ for (i = 0; t->input[t->offset + i] &&
+ (isalpha((unsigned char)t->input[t->offset + i])
+ ); i++);
+
+ if (i && t->input[t->offset + i]) {
+ tid = TK_SRVVARNAME;
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid character in condition");
+ return -1;
+ }
+ } else {
+ if (isdigit((unsigned char)c)) {
+ /* take all digits */
+ for (i = 0; t->input[t->offset + i] && isdigit((unsigned char)t->input[t->offset + i]); i++);
+
+ /* was there it least a digit ? */
+ if (i && t->input[t->offset + i]) {
+ tid = TK_INTEGER;
+
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "unexpected EOF");
+
+ return -1;
+ }
+ } else {
+ /* ERROR */
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t->line, "pos:", t->line_pos,
+ "invalid value field");
+
+ return -1;
+ }
+ }
+ break;
+ }
+ }
+
+ if (tid) {
+ *token_id = tid;
+
+ return 1;
+ } else if (t->offset < t->size) {
+ fprintf(stderr, "%s.%d: %d, %s\n",
+ __FILE__, __LINE__,
+ tid, token->ptr);
+ }
+ return 0;
+}
+
+int config_read(server *srv, const char *fn) {
+ stream s;
+ tokenizer_t t;
+ void *pParser;
+ int token_id;
+ buffer *token;
+ config_t context;
+ data_config *dc;
+ int ret;
+ buffer *bfn = buffer_init_string(fn);
+
+ if (0 != stream_open(&s, bfn)) {
+ buffer_free(bfn);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssss",
+ "opening configfile ", fn, "failed:", strerror(errno));
+ return -1;
+ }
+
+ buffer_free(bfn);
+
+ t.input = s.start;
+ t.offset = 0;
+ t.size = s.size;
+ t.line = 1;
+ t.line_pos = 1;
+
+ t.in_key = 1;
+ t.in_brace = 0;
+ t.in_cond = 0;
+
+ context.ok = 1;
+ context.config = srv->config_context;
+
+ dc = data_config_init();
+ buffer_copy_string(dc->key, "global");
+ array_insert_unique(srv->config_context, (data_unset *)dc);
+
+ context.ctx_name = dc->key;
+ context.ctx_config = dc->value;
+
+ /* default context */
+ srv->config = dc->value;
+
+ pParser = configparserAlloc( malloc );
+ token = buffer_init();
+ while((1 == (ret = config_tokenizer(srv, &t, &token_id, token))) && context.ok) {
+ configparser(pParser, token_id, token, &context);
+
+ token = buffer_init();
+ }
+ configparser(pParser, 0, token, &context);
+ configparserFree(pParser, free );
+
+ buffer_free(token);
+
+ stream_close(&s);
+
+ if (ret == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "configfile parser failed");
+ return -1;
+ }
+
+ if (context.ok == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsds",
+ "line:", t.line, "pos:", t.line_pos,
+ "parser failed somehow near here");
+ return -1;
+ }
+
+ if (0 != config_insert(srv)) {
+ return -1;
+ }
+
+ if (NULL != (dc = (data_config *)array_get_element(srv->config_context, "global"))) {
+ srv->config = dc->value;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+int config_set_defaults(server *srv) {
+ size_t i;
+ specific_config *s = srv->config_storage[0];
+
+ struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
+ {
+ /* - poll is most reliable
+ * - select works everywhere
+ * - linux-* are experimental
+ */
+#ifdef USE_POLL
+ { FDEVENT_HANDLER_POLL, "poll" },
+#endif
+#ifdef USE_SELECT
+ { FDEVENT_HANDLER_SELECT, "select" },
+#endif
+#ifdef USE_LINUX_EPOLL
+ { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
+#endif
+#ifdef USE_LINUX_SIGIO
+ { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" },
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
+#endif
+ { FDEVENT_HANDLER_UNSET, NULL }
+ };
+
+#ifdef USE_LICENSE
+ license_t *l;
+
+ if (srv->srvconf.license->used == 0) {
+ /* license is missing */
+ return -1;
+ }
+
+ l = license_init();
+
+ if (0 != license_parse(l, srv->srvconf.license)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing license information failed", srv->srvconf.license);
+
+ license_free(l);
+ return -1;
+ }
+ if (!license_is_valid(l)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "license is not valid");
+
+ license_free(l);
+ return -1;
+ }
+ license_free(l);
+#endif
+ if (srv->srvconf.port == 0) {
+ srv->srvconf.port = s->is_ssl ? 443 : 80;
+ }
+
+ if (srv->srvconf.event_handler->used == 0) {
+ /* choose a good default
+ *
+ * the event_handler list is sorted by 'goodness'
+ * taking the first available should be the best solution
+ */
+ srv->event_handler = event_handlers[0].et;
+
+ if (FDEVENT_HANDLER_UNSET == srv->event_handler) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "sorry, there is no event handler for this system");
+
+ return -1;
+ }
+ } else {
+ /*
+ * User override
+ */
+
+ for (i = 0; event_handlers[i].name; i++) {
+ if (0 == strcmp(event_handlers[i].name, srv->srvconf.event_handler->ptr)) {
+ srv->event_handler = event_handlers[i].et;
+ break;
+ }
+ }
+
+ if (FDEVENT_HANDLER_UNSET == srv->event_handler) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "the selected event-handler in unknown or not supported:",
+ srv->srvconf.event_handler );
+
+ return -1;
+ }
+ }
+
+ if (s->is_ssl) {
+ if (s->ssl_pemfile->used == 0) {
+ /* PEM file is require */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ssl.pemfile has to be set");
+ return -1;
+ }
+
+#ifndef USE_OPENSSL
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ssl support is missing, recompile with --with-openssl");
+
+ return -1;
+#endif
+ }
+
+ return 0;
+}
diff --git a/src/configfile.h b/src/configfile.h
new file mode 100644
index 00000000..55a13d5e
--- /dev/null
+++ b/src/configfile.h
@@ -0,0 +1,18 @@
+#ifndef _CONFIG_PARSER_H_
+#define _CONFIG_PARSER_H_
+
+#include "array.h"
+#include "buffer.h"
+
+typedef struct {
+ int ok;
+ array *config;
+ buffer *ctx_name;
+ array *ctx_config;
+} config_t;
+
+void *configparserAlloc(void *(*mallocProc)(size_t));
+void configparserFree(void *p, void (*freeProc)(void*));
+void configparser(void *yyp, int yymajor, buffer *yyminor, config_t *ctx);
+
+#endif
diff --git a/src/configparser.h b/src/configparser.h
new file mode 100644
index 00000000..c7e86ae1
--- /dev/null
+++ b/src/configparser.h
@@ -0,0 +1,19 @@
+#define TK_EOL 1
+#define TK_ASSIGN 2
+#define TK_LKEY 3
+#define TK_STRING 4
+#define TK_INTEGER 5
+#define TK_LPARAN 6
+#define TK_RPARAN 7
+#define TK_COMMA 8
+#define TK_ARRAY_ASSIGN 9
+#define TK_LCURLY 10
+#define TK_RCURLY 11
+#define TK_DOLLAR 12
+#define TK_SRVVARNAME 13
+#define TK_LBRACKET 14
+#define TK_RBRACKET 15
+#define TK_EQ 16
+#define TK_MATCH 17
+#define TK_NE 18
+#define TK_NOMATCH 19
diff --git a/src/configparser.y b/src/configparser.y
new file mode 100644
index 00000000..3d424ce3
--- /dev/null
+++ b/src/configparser.y
@@ -0,0 +1,164 @@
+%token_prefix TK_
+%token_type {buffer *}
+%extra_argument {config_t *ctx}
+%name configparser
+
+%include {
+#include <assert.h>
+#include "config.h"
+#include "configfile.h"
+#include "buffer.h"
+#include "array.h"
+}
+
+%parse_failure {
+ ctx->ok = 0;
+}
+
+input ::= metalines.
+metalines ::= metalines metaline.
+metalines ::= .
+metaline ::= varline.
+metaline ::= condline.
+metaline ::= EOL.
+
+%type value {data_unset *}
+%type aelement {data_unset *}
+%type aelements {array *}
+%type array {array *}
+%type cond {config_cond_t }
+%token_destructor { buffer_free($$); }
+
+varline ::= key(A) ASSIGN value(B). {
+ buffer_copy_string_buffer(B->key, A);
+ array_insert_unique(ctx->ctx_config, B);
+ buffer_free(A);
+}
+
+key(A) ::= LKEY(B). {
+ A = B;
+ B = NULL;
+}
+
+value(A) ::= STRING(B). {
+ A = (data_unset *)data_string_init();
+ buffer_copy_string_buffer(((data_string *)(A))->value, B);
+ buffer_free(B);
+}
+
+value(A) ::= INTEGER(B). {
+ A = (data_unset *)data_integer_init();
+ ((data_integer *)(A))->value = strtol(B->ptr, NULL, 10);
+ buffer_free(B);
+}
+value(A) ::= array(B). {
+ A = (data_unset *)data_array_init();
+ array_free(((data_array *)(A))->value);
+ ((data_array *)(A))->value = B;
+}
+array(A) ::= LPARAN aelements(B) RPARAN. {
+ A = B;
+ B = NULL;
+}
+
+aelements(A) ::= aelements(C) COMMA aelement(B). {
+ array_insert_unique(C, B);
+
+ A = C;
+}
+
+aelements(A) ::= aelements(C) COMMA. {
+ A = C;
+}
+
+aelements(A) ::= aelement(B). {
+ A = array_init();
+ array_insert_unique(A, B);
+}
+
+aelement(A) ::= value(B). {
+ A = B;
+ B = NULL;
+}
+aelement(A) ::= STRING(B) ARRAY_ASSIGN value(C). {
+ buffer_copy_string_buffer(C->key, B);
+ buffer_free(B);
+
+ A = C;
+ C = NULL;
+}
+condline ::= context LCURLY metalines RCURLY EOL. {
+ data_config *dc;
+
+ dc = (data_config *)array_get_element(ctx->config, "global");
+ assert(dc);
+ ctx->ctx_name = dc->key;
+ ctx->ctx_config = dc->value;
+}
+
+context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). {
+ data_config *dc;
+ buffer *b;
+
+ b = buffer_init();
+ buffer_copy_string_buffer(b, B);
+ buffer_append_string_buffer(b, C);
+ buffer_append_string_buffer(b, D);
+ buffer_append_long(b, E);
+
+ if (NULL != (dc = (data_config *)array_get_element(ctx->config, b->ptr))) {
+ ctx->ctx_name = dc->key;
+ ctx->ctx_config = dc->value;
+ } else {
+ dc = data_config_init();
+
+ buffer_copy_string_buffer(dc->key, b);
+ buffer_copy_string_buffer(dc->comp_key, B);
+ buffer_append_string_buffer(dc->comp_key, C);
+ dc->cond = E;
+
+ switch(E) {
+ case CONFIG_COND_NE:
+ case CONFIG_COND_EQ:
+ dc->match.string = buffer_init_string(D->ptr);
+ break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_NOMATCH:
+ case CONFIG_COND_MATCH: {
+ const char *errptr;
+ int erroff;
+
+ if (NULL == (dc->match.regex =
+ pcre_compile(D->ptr, 0, &errptr, &erroff, NULL))) {
+ dc->match.string = buffer_init_string(errptr);
+ dc->cond = CONFIG_COND_UNSET;
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+
+ array_insert_unique(ctx->config, (data_unset *)dc);
+
+ ctx->ctx_name = dc->key;
+ ctx->ctx_config = dc->value;
+ }
+ buffer_free(b);
+ buffer_free(B);
+ buffer_free(C);
+ buffer_free(D);
+}
+cond(A) ::= EQ. {
+ A = CONFIG_COND_EQ;
+}
+cond(A) ::= MATCH. {
+ A = CONFIG_COND_MATCH;
+}
+cond(A) ::= NE. {
+ A = CONFIG_COND_NE;
+}
+cond(A) ::= NOMATCH. {
+ A = CONFIG_COND_NOMATCH;
+}
diff --git a/src/connections.c b/src/connections.c
new file mode 100644
index 00000000..5f1ff544
--- /dev/null
+++ b/src/connections.c
@@ -0,0 +1,1585 @@
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "log.h"
+#include "connections.h"
+#include "fdevent.h"
+
+#include "request.h"
+#include "response.h"
+#include "network.h"
+#include "http_chunk.h"
+#include "file_cache.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#endif
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+const char *connection_get_state(connection_state_t state) {
+ switch (state) {
+ case CON_STATE_CONNECT: return "connect";
+ case CON_STATE_READ: return "read";
+ case CON_STATE_READ_POST: return "readpost";
+ case CON_STATE_WRITE: return "write";
+ case CON_STATE_CLOSE: return "close";
+ case CON_STATE_ERROR: return "error";
+ case CON_STATE_HANDLE_REQUEST: return "handle-req";
+ case CON_STATE_REQUEST_START: return "req-start";
+ case CON_STATE_REQUEST_END: return "req-end";
+ case CON_STATE_RESPONSE_START: return "resp-start";
+ case CON_STATE_RESPONSE_END: return "resp-end";
+ default: return "(unknown)";
+ }
+}
+
+const char *connection_get_short_state(connection_state_t state) {
+ switch (state) {
+ case CON_STATE_CONNECT: return ".";
+ case CON_STATE_READ: return "r";
+ case CON_STATE_READ_POST: return "R";
+ case CON_STATE_WRITE: return "W";
+ case CON_STATE_CLOSE: return "C";
+ case CON_STATE_ERROR: return "E";
+ case CON_STATE_HANDLE_REQUEST: return "h";
+ case CON_STATE_REQUEST_START: return "q";
+ case CON_STATE_REQUEST_END: return "Q";
+ case CON_STATE_RESPONSE_START: return "s";
+ case CON_STATE_RESPONSE_END: return "S";
+ default: return "x";
+ }
+}
+
+static connection *connections_get_new_connection(server *srv) {
+ connections *conns = srv->conns;
+ size_t i;
+
+ if (conns->size == 0) {
+ conns->size = 128;
+ conns->ptr = NULL;
+ conns->ptr = malloc(sizeof(*conns->ptr) * conns->size);
+ for (i = 0; i < conns->size; i++) {
+ conns->ptr[i] = connection_init(srv);
+ }
+ } else if (conns->size == conns->used) {
+ conns->size += 128;
+ conns->ptr = realloc(conns->ptr, sizeof(*conns->ptr) * conns->size);
+
+ for (i = conns->used; i < conns->size; i++) {
+ conns->ptr[i] = connection_init(srv);
+ }
+ }
+
+ connection_reset(srv, conns->ptr[conns->used]);
+#if 0
+ fprintf(stderr, "%s.%d: add: ", __FILE__, __LINE__);
+ for (i = 0; i < conns->used + 1; i++) {
+ fprintf(stderr, "%d ", conns->ptr[i]->fd);
+ }
+ fprintf(stderr, "\n");
+#endif
+
+ conns->ptr[conns->used]->ndx = conns->used;
+ return conns->ptr[conns->used++];
+}
+
+static int connection_del(server *srv, connection *con) {
+ size_t i;
+ connections *conns = srv->conns;
+ connection *temp;
+
+ if (con == NULL) return -1;
+
+ if (-1 == con->ndx) return -1;
+
+ i = con->ndx;
+
+ /* not last element */
+
+ if (i != conns->used - 1) {
+ temp = conns->ptr[i];
+ conns->ptr[i] = conns->ptr[conns->used - 1];
+ conns->ptr[conns->used - 1] = temp;
+
+ conns->ptr[i]->ndx = i;
+ conns->ptr[conns->used - 1]->ndx = -1;
+ }
+
+ conns->used--;
+
+ con->ndx = -1;
+#if 0
+ fprintf(stderr, "%s.%d: del: (%d)", __FILE__, __LINE__, conns->used);
+ for (i = 0; i < conns->used; i++) {
+ fprintf(stderr, "%d ", conns->ptr[i]->fd);
+ }
+ fprintf(stderr, "\n");
+#endif
+ return 0;
+}
+
+int connection_close(server *srv, connection *con) {
+#ifdef USE_OPENSSL
+ server_socket *srv_sock = con->srv_socket;
+#endif
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ if (con->ssl) SSL_free(con->ssl);
+ con->ssl = NULL;
+ }
+#endif
+
+ fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
+ fdevent_unregister(srv->ev, con->fd);
+#ifdef __WIN32
+ if (closesocket(con->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "(warning) close:", con->fd, strerror(errno));
+ }
+#else
+ if (close(con->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "(warning) close:", con->fd, strerror(errno));
+ }
+#endif
+
+ srv->cur_fds--;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "closed()", con->fd);
+#endif
+
+ connection_del(srv, con);
+ connection_set_state(srv, con, CON_STATE_CONNECT);
+
+ return 0;
+}
+
+static void dump_packet(const unsigned char *data, size_t len) {
+ size_t i, j;
+
+ if (len == 0) return;
+
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0) fprintf(stderr, " ");
+
+ fprintf(stderr, "%02x ", data[i]);
+
+ if ((i + 1) % 16 == 0) {
+ fprintf(stderr, " ");
+ for (j = 0; j <= i % 16; j++) {
+ unsigned char c;
+
+ if (i-15+j >= len) break;
+
+ c = data[i-15+j];
+
+ fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
+ }
+
+ fprintf(stderr, "\n");
+ }
+ }
+
+ if (len % 16 != 0) {
+ for (j = i % 16; j < 16; j++) {
+ fprintf(stderr, " ");
+ }
+
+ fprintf(stderr, " ");
+ for (j = i & ~0xf; j < len; j++) {
+ unsigned char c;
+
+ c = data[j];
+ fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
+ }
+ fprintf(stderr, "\n");
+ }
+}
+
+static int connection_handle_read(server *srv, connection *con) {
+ int len;
+ buffer *b;
+#ifdef USE_OPENSSL
+ server_socket *srv_sock = con->srv_socket;
+#endif
+
+ b = chunkqueue_get_append_buffer(con->read_queue);
+ buffer_prepare_copy(b, 4096);
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ len = SSL_read(con->ssl, b->ptr, b->size - 1);
+ } else {
+ len = read(con->fd, b->ptr, b->size - 1);
+ }
+#elif defined(__WIN32)
+ len = recv(con->fd, b->ptr, b->size - 1, 0);
+#else
+ len = read(con->fd, b->ptr, b->size - 1);
+#endif
+
+ if (len < 0) {
+ con->is_readable = 0;
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ int r;
+
+ switch ((r = SSL_get_error(con->ssl, len))) {
+ case SSL_ERROR_WANT_READ:
+ return 0;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
+ r, ERR_error_string(ERR_get_error(), NULL));
+ break;
+ }
+ } else {
+ if (errno == EAGAIN) return 0;
+ if (errno == EINTR) {
+ /* we have been interrupted before we could read */
+ con->is_readable = 1;
+ return 0;
+ }
+
+ if (errno != ECONNRESET) {
+ /* expected for keep-alive */
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
+ }
+ }
+#else
+ if (errno == EAGAIN) return 0;
+ if (errno == EINTR) {
+ /* we have been interrupted before we could read */
+ con->is_readable = 1;
+ return 0;
+ }
+
+ if (errno != ECONNRESET) {
+ /* expected for keep-alive */
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
+ }
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ return -1;
+ } else if (len == 0) {
+ con->is_readable = 0;
+ /* the other end close the connection -> KEEP-ALIVE */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "connection closed: remote site closed unexpectedly");
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ return -1;
+ } else if ((size_t)len < b->size - 1) {
+ /* we got less then expected, wait for the next fd-event */
+
+ con->is_readable = 0;
+ }
+
+ b->used = len;
+ b->ptr[b->used++] = '\0';
+
+ con->bytes_read += len;
+#if 0
+ dump_packet(b->ptr, len);
+#endif
+
+ return 0;
+}
+
+static int connection_handle_write_prepare(server *srv, connection *con) {
+ struct stat st;
+ int s_len;
+
+ switch(con->mode) {
+ case DIRECT:
+ switch(con->http_status) {
+ case 400: /* class: header + custom body */
+ case 401:
+ case 403:
+ case 404:
+ case 408:
+ case 411:
+ case 416:
+ case 500:
+ case 501:
+ case 503:
+ case 505: {
+ con->file_finished = 1;
+
+ /* rewrite the filename */
+
+ /* FIXME: use con.physical.errorfile
+ *
+ *
+ */
+ buffer_reset(con->physical.path);
+
+ if (srv->srvconf.errorfile_prefix->used) {
+ buffer_copy_string_buffer(con->physical.path, srv->srvconf.errorfile_prefix);
+ buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status));
+ }
+
+ if ((con->physical.path->used <= 1) ||
+ (-1 == (stat(con->physical.path->ptr, &st)))) {
+ buffer *b;
+
+ buffer_reset(con->physical.path);
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ /* build default error-page */
+ buffer_copy_string(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>");
+ buffer_append_long(b, con->http_status);
+ buffer_append_string(b, " - ");
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ buffer_append_string(b,
+ "</title>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h1>");
+ buffer_append_long(b, con->http_status);
+ buffer_append_string(b, " - ");
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ buffer_append_string(b,"</h1>\n"
+ " </body>\n"
+ "</html>\n"
+ );
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+ } else {
+ /* get content-type */
+ size_t k;
+ s_len = con->physical.path->used - 1;
+
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ int ct_len = ds->key->used - 1;
+
+ if (s_len < ct_len ||
+ ds->key->used == 0) continue;
+
+ if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(ds->value));
+ break;
+ }
+ }
+
+ if (k == con->conf.mimetypes->used) {
+ /* the error message should be HTML */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+ }
+ }
+ }
+ /* fall through */
+
+ case 200: /* class: header + body */
+ if (con->physical.path->used) {
+ con->file_finished = 1;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), con->physical.path);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ return -1;
+ }
+
+ if (S_ISREG(con->fce->st.st_mode)) {
+ if (con->request.http_method == HTTP_METHOD_GET ||
+ con->request.http_method == HTTP_METHOD_POST) {
+ http_chunk_append_file(srv, con, con->physical.path, 0, con->fce->st.st_size);
+ con->response.content_length = http_chunkqueue_length(srv, con);
+ } else if (con->request.http_method == HTTP_METHOD_HEAD) {
+ con->response.content_length = con->fce->st.st_size;
+ } else {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ return -1;
+ }
+
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ con->fce->st.st_mtime);
+
+
+ } else {
+ /* why the heck ? */
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "connection closed: no regular-file to send:",
+ con->physical.path);
+
+ con->file_finished = 1;
+ }
+ } else {
+ if (con->file_finished) {
+ con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con);
+ }
+
+ /* disable keep-alive if size-info for the body is missing */
+ if ((con->parsed_response & HTTP_CONTENT_LENGTH) &&
+ ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) {
+ con->keep_alive = 0;
+ }
+
+ if (con->request.http_method == HTTP_METHOD_HEAD) {
+ chunkqueue_reset(con->write_queue);
+ }
+
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ 0);
+
+ }
+ break;
+
+ case 206: /* write_queue is already prepared */
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ 0);
+ con->file_finished = 1;
+ break;
+ case 302:
+ con->file_finished = 1;
+
+ con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con);
+
+ http_response_write_header(srv, con,
+ con->response.content_length,
+ 0);
+
+ break;
+ case 205: /* class: header only */
+ case 301:
+ case 304:
+ default:
+ /* disable chunked encoding again as we have no body */
+ con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
+ chunkqueue_reset(con->write_queue);
+
+ http_response_write_header(srv, con, 0, 0);
+ con->file_finished = 1;
+ break;
+ }
+
+ break;
+ default:
+ if (con->request.http_method == HTTP_METHOD_HEAD ||
+ con->http_status == 301 ||
+ con->http_status == 304 ||
+ con->http_status == 205) {
+ /* remove possible chunks */
+ con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
+ chunkqueue_reset(con->write_queue);
+ }
+
+ if (0 == (con->parsed_response & HTTP_CONNECTION)) {
+ /* (f)cgi did'nt send Connection: header
+ *
+ * shall we ?
+ */
+ if (((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) &&
+ (con->parsed_response & HTTP_CONTENT_LENGTH) == 0) {
+ /* without content_length, no keep-alive */
+
+ con->keep_alive = 0;
+ }
+ } else {
+ /* a subrequest disable keep-alive although the client wanted it */
+ if (con->keep_alive && !con->response.keep_alive) {
+ con->keep_alive = 0;
+
+ /* FIXME: we have to drop the Connection: Header from the subrequest */
+ }
+ }
+
+ con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con);
+ http_response_write_basic_header(srv, con);
+
+ break;
+ }
+
+ return 0;
+}
+
+static int connection_handle_write(server *srv, connection *con) {
+ switch(network_write_chunkqueue(srv, con, con->write_queue)) {
+ case 0:
+ if (con->file_finished) {
+ connection_set_state(srv, con, CON_STATE_RESPONSE_END);
+ joblist_append(srv, con);
+ }
+ break;
+ case -1: /* error on our side */
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: write failed on fd", con->fd);
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ joblist_append(srv, con);
+ break;
+ case -2: /* remote close */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ joblist_append(srv, con);
+ break;
+ case 1:
+ con->is_writable = 0;
+
+ /* not finished yet -> WRITE */
+ break;
+ }
+
+ return 0;
+}
+
+
+
+connection *connection_init(server *srv) {
+ connection *con;
+
+ UNUSED(srv);
+
+ con = calloc(1, sizeof(*con));
+
+ con->fd = 0;
+ con->ndx = -1;
+ con->fde_ndx = -1;
+ con->bytes_written = 0;
+ con->bytes_read = 0;
+ con->bytes_header = 0;
+
+
+#define CLEAN(x) \
+ con->x = buffer_init();
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.request);
+ CLEAN(request.pathinfo);
+ CLEAN(request.content);
+
+ CLEAN(request.orig_uri);
+
+ CLEAN(uri.scheme);
+ CLEAN(uri.authority);
+ CLEAN(uri.path);
+ CLEAN(uri.path_raw);
+ CLEAN(uri.query);
+
+ CLEAN(physical.doc_root);
+ CLEAN(physical.path);
+ CLEAN(physical.rel_path);
+ CLEAN(physical.etag);
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+
+#undef CLEAN
+ con->write_queue = chunkqueue_init();
+ con->read_queue = chunkqueue_init();
+ con->request.headers = array_init();
+ con->response.headers = array_init();
+ con->environment = array_init();
+
+ /* init plugin specific connection structures */
+
+ con->plugin_ctx = calloc(srv->plugins.used + 1, sizeof(void *));
+
+ config_setup_connection(srv, con);
+
+ return con;
+}
+
+void connections_free(server *srv) {
+ connections *conns = srv->conns;
+ size_t i;
+
+ for (i = 0; i < conns->size; i++) {
+ connection *con = conns->ptr[i];
+
+ connection_reset(srv, con);
+
+ chunkqueue_free(con->write_queue);
+ chunkqueue_free(con->read_queue);
+ array_free(con->request.headers);
+ array_free(con->response.headers);
+ array_free(con->environment);
+
+#define CLEAN(x) \
+ buffer_free(con->x);
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.request);
+ CLEAN(request.pathinfo);
+ CLEAN(request.content);
+
+ CLEAN(request.orig_uri);
+
+ CLEAN(uri.scheme);
+ CLEAN(uri.authority);
+ CLEAN(uri.path);
+ CLEAN(uri.path_raw);
+ CLEAN(uri.query);
+
+ CLEAN(physical.doc_root);
+ CLEAN(physical.path);
+ CLEAN(physical.etag);
+ CLEAN(physical.rel_path);
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+#undef CLEAN
+ free(con->plugin_ctx);
+
+ free(con);
+ }
+
+ free(conns->ptr);
+}
+
+
+int connection_reset(server *srv, connection *con) {
+ size_t i;
+
+ plugins_call_connection_reset(srv, con);
+
+ con->is_readable = 1;
+ con->is_writable = 1;
+ con->http_status = 0;
+ con->file_finished = 0;
+ con->file_started = 0;
+ con->got_response = 0;
+
+ con->parsed_response = 0;
+
+ con->bytes_written = 0;
+ con->bytes_written_cur_second = 0;
+ con->bytes_read = 0;
+ con->bytes_header = 0;
+
+ con->request.http_method = HTTP_METHOD_UNSET;
+ con->request.http_version = HTTP_VERSION_UNSET;
+
+ con->request.http_if_modified_since = NULL;
+ con->request.http_if_none_match = NULL;
+
+ con->response.keep_alive = 0;
+ con->response.content_length = -1;
+ con->response.transfer_encoding = 0;
+
+ con->mode = DIRECT;
+
+#define CLEAN(x) \
+ if (con->x) buffer_reset(con->x);
+
+ CLEAN(request.uri);
+ CLEAN(request.request_line);
+ CLEAN(request.pathinfo);
+ CLEAN(request.content);
+ CLEAN(request.request);
+
+ CLEAN(request.orig_uri);
+
+ CLEAN(uri.scheme);
+ CLEAN(uri.authority);
+ CLEAN(uri.path);
+ CLEAN(uri.path_raw);
+ CLEAN(uri.query);
+
+ CLEAN(physical.doc_root);
+ CLEAN(physical.path);
+ CLEAN(physical.rel_path);
+ CLEAN(physical.etag);
+
+ CLEAN(parse_request);
+
+ CLEAN(authed_user);
+ CLEAN(server_name);
+ CLEAN(error_handler);
+#undef CLEAN
+
+#define CLEAN(x) \
+ if (con->x) con->x->used = 0;
+
+#undef CLEAN
+
+#define CLEAN(x) \
+ con->request.x = NULL;
+
+ CLEAN(http_host);
+ CLEAN(http_range);
+ CLEAN(http_content_type);
+#undef CLEAN
+ con->request.content_length = 0;
+
+ array_reset(con->request.headers);
+ array_reset(con->response.headers);
+ array_reset(con->environment);
+
+ chunkqueue_reset(con->write_queue);
+
+ if (con->fce) {
+ file_cache_entry_release(srv, con, con->fce);
+ con->fce = NULL;
+ }
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ con->plugin_ctx[0] = NULL;
+ }
+
+ con->header_len = 0;
+ con->in_error_handler = 0;
+
+ config_setup_connection(srv, con);
+
+ return 0;
+}
+
+/**
+ *
+ * search for \r\n\r\n
+ *
+ * this is a special 32bit version which is using a sliding window for
+ * the comparisions
+ *
+ * how it works:
+ *
+ * b: 'abcdefg'
+ * rnrn: 'cdef'
+ *
+ * cmpbuf: abcd != cdef
+ * cmpbuf: bcde != cdef
+ * cmpbuf: cdef == cdef -> return &c
+ *
+ * cmpbuf and rnrn are treated as 32bit uint and bit-ops are used to
+ * maintain cmpbuf and rnrn
+ *
+ */
+
+char *buffer_search_rnrn(buffer *b) {
+ uint32_t cmpbuf, rnrn;
+ char *cp;
+ size_t i;
+
+ if (b->used < 4) return NULL;
+
+ rnrn = ('\r' << 24) | ('\n' << 16) |
+ ('\r' << 8) | ('\n' << 0);
+
+ cmpbuf = (b->ptr[0] << 24) | (b->ptr[1] << 16) |
+ (b->ptr[2] << 8) | (b->ptr[3] << 0);
+
+ cp = b->ptr + 4;
+ for (i = 0; i < b->used - 4; i++) {
+ if (cmpbuf == rnrn) return cp - 4;
+
+ cmpbuf = (cmpbuf << 8 | *(cp++)) & 0xffffffff;
+ }
+
+ return NULL;
+}
+
+int connection_handle_read_state(server *srv, connection *con) {
+ int ostate = con->state;
+ char *h_term = NULL;
+ chunk *c;
+ chunkqueue *cq = con->read_queue;
+
+ if (con->is_readable) {
+ con->read_idle_ts = srv->cur_ts;
+
+ if (0 != connection_handle_read(srv, con)) {
+ return -1;
+ }
+ }
+
+ /* move the empty chunks out of the way */
+ for (c = cq->first; c; c = cq->first) {
+ assert(c != c->next);
+
+ if (c->data.mem->used == 0) {
+ cq->first = c->next;
+ c->next = cq->unused;
+ cq->unused = c;
+
+ if (cq->first == NULL) cq->last = NULL;
+
+ c = cq->first;
+ } else {
+ break;
+ }
+ }
+
+ /* nothing to handle */
+ if (cq->first == NULL) return 0;
+
+ switch(ostate) {
+ case CON_STATE_READ:
+ /* prepare con->request.request */
+ c = cq->first;
+
+ /* check if we need the full package */
+ if (con->request.request->used == 0) {
+ buffer b;
+
+ b.ptr = c->data.mem->ptr + c->offset;
+ b.used = c->data.mem->used - c->offset;
+
+ if (NULL != (h_term = buffer_search_rnrn(&b))) {
+ /* \r\n\r\n found
+ * - copy everything incl. the terminator to request.request
+ */
+
+ buffer_copy_string_len(con->request.request,
+ b.ptr,
+ h_term - b.ptr + 4);
+
+ /* the buffer has been read up to the terminator */
+ c->offset += h_term - b.ptr + 4;
+ } else {
+ /* not found, copy everything */
+ buffer_copy_string_len(con->request.request, c->data.mem->ptr + c->offset, c->data.mem->used - c->offset - 1);
+ c->offset = c->data.mem->used - 1;
+ }
+ } else {
+ /* have to take care of overlapping header terminators */
+
+ size_t l = con->request.request->used - 2;
+ char *s = con->request.request->ptr;
+ buffer b;
+
+ b.ptr = c->data.mem->ptr + c->offset;
+ b.used = c->data.mem->used - c->offset;
+
+ if (con->request.request->used - 1 > 3 &&
+ c->data.mem->used > 1 &&
+ s[l-2] == '\r' &&
+ s[l-1] == '\n' &&
+ s[l-0] == '\r' &&
+ c->data.mem->ptr[0] == '\n') {
+ buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 1);
+ c->offset += 1;
+
+ h_term = con->request.request->ptr;
+ } else if (con->request.request->used - 1 > 2 &&
+ c->data.mem->used > 2 &&
+ s[l-1] == '\r' &&
+ s[l-0] == '\n' &&
+ c->data.mem->ptr[0] == '\r' &&
+ c->data.mem->ptr[1] == '\n') {
+ buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 2);
+ c->offset += 2;
+
+ h_term = con->request.request->ptr;
+ } else if (con->request.request->used - 1 > 1 &&
+ c->data.mem->used > 3 &&
+ s[l-0] == '\r' &&
+ c->data.mem->ptr[0] == '\n' &&
+ c->data.mem->ptr[1] == '\r' &&
+ c->data.mem->ptr[2] == '\n') {
+ buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 3);
+ c->offset += 3;
+
+ h_term = con->request.request->ptr;
+ } else if (NULL != (h_term = buffer_search_string_len(&b, "\r\n\r\n", 4))) {
+ /* \r\n\r\n found
+ * - copy everything incl. the terminator to request.request
+ */
+
+ buffer_append_string_len(con->request.request,
+ c->data.mem->ptr + c->offset,
+ c->offset + h_term - b.ptr + 4);
+
+ /* the buffer has been read up to the terminator */
+ c->offset += h_term - b.ptr + 4;
+ } else {
+ /* not found, copy everything */
+ buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, c->data.mem->used - c->offset - 1);
+ c->offset = c->data.mem->used - 1;
+ }
+ }
+
+ if (c->offset + 1 == c->data.mem->used) {
+ /* chunk is empty, move it to unused */
+ cq->first = c->next;
+ c->next = cq->unused;
+ cq->unused = c;
+
+ if (cq->first == NULL) cq->last = NULL;
+
+ assert(c != c->next);
+ }
+
+ /* con->request.request is setup up */
+ if (h_term) {
+ connection_set_state(srv, con, CON_STATE_REQUEST_END);
+ } else if (chunkqueue_length(cq) > 64 * 1024) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "http-header larger then 64k -> disconnected", chunkqueue_length(cq));
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+ break;
+ case CON_STATE_READ_POST:
+ for (c = cq->first; c && (con->request.content->used != con->request.content_length + 1); c = cq->first) {
+ off_t weWant, weHave, toRead;
+
+ weWant = con->request.content_length - (con->request.content->used ? con->request.content->used - 1 : 0);
+ /* without the terminating \0 */
+
+ assert(c->data.mem->used);
+
+ weHave = c->data.mem->used - c->offset - 1;
+
+ toRead = weHave > weWant ? weWant : weHave;
+
+ buffer_append_string_len(con->request.content, c->data.mem->ptr + c->offset, toRead);
+
+ c->offset += toRead;
+
+ if (c->offset + 1 >= c->data.mem->used) {
+ /* chunk is empty, move it to unused */
+
+ cq->first = c->next;
+ c->next = cq->unused;
+ cq->unused = c;
+
+ if (cq->first == NULL) cq->last = NULL;
+
+ assert(c != c->next);
+ } else {
+ assert(toRead);
+ }
+ }
+
+ /* Content is ready */
+ if (con->request.content->used == con->request.content_length + 1) {
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+handler_t connection_handle_fdevent(void *s, void *context, int revents) {
+ server *srv = (server *)s;
+ connection *con = context;
+
+ joblist_append(srv, con);
+
+ if (revents & FDEVENT_IN) {
+ con->is_readable = 1;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "read-wait - done", con->fd);
+#endif
+ }
+ if (revents & FDEVENT_OUT) {
+ con->is_writable = 1;
+ /* we don't need the event twice */
+ }
+
+
+ if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
+ /* looks like an error */
+
+ /* FIXME: revents = 0x19 still means that we should read from the queue */
+ if (revents & FDEVENT_HUP) {
+ if (con->state == CON_STATE_CLOSE) {
+ con->close_timeout_ts = 0;
+ } else {
+ /* sigio reports the wrong event here
+ *
+ * there was no HUP at all
+ */
+#ifdef USE_LINUX_SIGIO
+ if (srv->ev->in_sigio == 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> HUP", con->fd);
+ } else {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+#else
+ connection_set_state(srv, con, CON_STATE_ERROR);
+#endif
+
+ }
+ } else if (revents & FDEVENT_ERR) {
+#ifndef USE_LINUX_SIGIO
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> ERR", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> ???", revents);
+ }
+ }
+
+ if (con->state == CON_STATE_READ ||
+ con->state == CON_STATE_READ_POST) {
+ connection_handle_read_state(srv, con);
+ }
+
+ if (con->state == CON_STATE_WRITE &&
+ !chunkqueue_is_empty(con->write_queue) &&
+ con->is_writable) {
+
+ if (-1 == connection_handle_write(srv, con)) {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+
+ log_error_write(srv, __FILE__, __LINE__, "ds",
+ con->fd,
+ "handle write failed.");
+ } else if (con->state == CON_STATE_WRITE) {
+ con->write_request_ts = srv->cur_ts;
+ }
+ }
+
+ if (con->state == CON_STATE_CLOSE) {
+ /* flush the read buffers */
+ int b;
+
+ if (ioctl(con->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "ioctl() failed", strerror(errno));
+ }
+
+ if (b > 0) {
+ char buf[1024];
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "CLOSE-read()", con->fd, b);
+
+ /* */
+ read(con->fd, buf, sizeof(buf));
+ } else {
+ /* nothing to read */
+
+ con->close_timeout_ts = 0;
+ }
+ }
+
+ return HANDLER_FINISHED;
+}
+
+
+connection *connection_accept(server *srv, server_socket *srv_socket) {
+ int accepted_requests = 0;
+ /* accept everything */
+
+ /* search an empty place */
+ int cnt;
+ sock_addr cnt_addr;
+ socklen_t cnt_len;
+ /* accept it and register the fd */
+
+ cnt_len = sizeof(cnt_addr);
+
+ if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {
+ if ((errno != EAGAIN) &&
+ (errno != EINTR)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "accept failed: ", strerror(errno));
+ }
+ return NULL;
+ } else {
+ connection *con;
+
+ srv->cur_fds++;
+
+ accepted_requests++;
+ /* ok, we have the connection, register it */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "appected()", cnt);
+#endif
+ srv->con_opened++;
+
+ con = connections_get_new_connection(srv);
+
+ con->fd = cnt;
+ con->fde_ndx = -1;
+#if 0
+ gettimeofday(&(con->start_tv), NULL);
+#endif
+ fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);
+
+ connection_set_state(srv, con, CON_STATE_REQUEST_START);
+
+ con->connection_start = srv->cur_ts;
+ con->dst_addr = cnt_addr;
+ con->srv_socket = srv_socket;
+
+ if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+ return NULL;
+ }
+#ifdef USE_OPENSSL
+ /* connect FD to SSL */
+ if (srv_socket->is_ssl) {
+ if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ return NULL;
+ } else {
+ SSL_set_accept_state(con->ssl);
+ }
+
+ if (1 != (SSL_set_fd(con->ssl, cnt))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NULL;
+ }
+ }
+#endif
+ return con;
+ }
+}
+
+int connection_set_state(server *srv, connection *con, connection_state_t state) {
+ UNUSED(srv);
+
+ con->state = state;
+
+ return 0;
+}
+
+
+int connection_state_machine(server *srv, connection *con) {
+ int done = 0, r;
+#ifdef USE_OPENSSL
+ server_socket *srv_sock = con->srv_socket;
+#endif
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state at start",
+ con->fd,
+ connection_get_state(con->state));
+ }
+
+ while (done == 0) {
+ size_t ostate = con->state;
+ int b;
+
+ switch (con->state) {
+ case CON_STATE_REQUEST_START: /* transient */
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ con->request_start = srv->cur_ts;
+ con->read_idle_ts = srv->cur_ts;
+
+ con->request_count++;
+
+ connection_set_state(srv, con, CON_STATE_READ);
+
+ break;
+ case CON_STATE_REQUEST_END: /* transient */
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ if (http_request_parse(srv, con)) {
+ /* we have to read some data from the POST request */
+
+ connection_set_state(srv, con, CON_STATE_READ_POST);
+
+ break;
+ }
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ break;
+ case CON_STATE_HANDLE_REQUEST:
+ /*
+ * the request is parsed
+ *
+ * decided what to do with the request
+ * -
+ *
+ *
+ */
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ switch (r = http_response_prepare(srv, con)) {
+ case HANDLER_FINISHED:
+ if (con->http_status == 404 ||
+ con->http_status == 403) {
+ /* 404 error-handler */
+
+ if (con->in_error_handler == 0 &&
+ (!buffer_is_empty(con->conf.error_handler) ||
+ !buffer_is_empty(con->error_handler))) {
+ /* call error-handler */
+
+ con->error_handler_saved_status = con->http_status;
+ con->http_status = 0;
+
+ if (buffer_is_empty(con->error_handler)) {
+ buffer_copy_string_buffer(con->request.uri, con->conf.error_handler);
+ } else {
+ buffer_copy_string_buffer(con->request.uri, con->error_handler);
+ }
+ buffer_reset(con->physical.path);
+
+ con->in_error_handler = 1;
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ done = -1;
+ break;
+ } else if (con->in_error_handler) {
+ /* error-handler is a 404 */
+
+ /* continue as normal, status is the same */
+ log_error_write(srv, __FILE__, __LINE__, "sb", "error-handler not found:", con->conf.error_handler);
+
+ con->http_status = con->error_handler_saved_status;
+ }
+ } else if (con->in_error_handler) {
+ /* error-handler is back and has generated content */
+ /* reset the old status */
+ if (con->http_status == 0) {
+ con->http_status = con->error_handler_saved_status;
+ }
+ }
+
+ if (con->http_status == 0) con->http_status = 200;
+
+ /* we have something to send, go on */
+ connection_set_state(srv, con, CON_STATE_RESPONSE_START);
+ break;
+ case HANDLER_WAIT_FOR_FD:
+ srv->want_fds++;
+
+ fdwaitqueue_append(srv, con);
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+
+ break;
+ case HANDLER_COMEBACK:
+ done = -1;
+ case HANDLER_WAIT_FOR_EVENT:
+ /* come back here */
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ break;
+ case HANDLER_ERROR:
+ /* something went wrong */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdd", "unknown ret-value: ", con->fd, r);
+ break;
+ }
+
+ break;
+ case CON_STATE_RESPONSE_START:
+ /*
+ * the decision is done
+ * - create the HTTP-Response-Header
+ *
+ */
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ connection_handle_write_prepare(srv, con);
+
+ connection_set_state(srv, con, CON_STATE_WRITE);
+ break;
+ case CON_STATE_RESPONSE_END: /* transient */
+ /* log the request */
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ plugins_call_handle_request_done(srv, con);
+
+ srv->con_written++;
+
+ if (con->keep_alive) {
+ connection_set_state(srv, con, CON_STATE_REQUEST_START);
+
+#if 0
+ con->request_start = srv->cur_ts;
+ con->read_idle_ts = srv->cur_ts;
+#endif
+ } else {
+ switch(r = plugins_call_handle_connection_close(srv, con)) {
+ case HANDLER_GO_ON:
+ case HANDLER_FINISHED:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandling return value", r);
+ break;
+ }
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ switch (SSL_shutdown(con->ssl)) {
+ case 1:
+ /* done */
+ break;
+ case 0:
+ /* wait for fd-event
+ *
+ * FIXME: wait for fdevent and call SSL_shutdown again
+ *
+ */
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ }
+#endif
+ connection_close(srv, con);
+
+ srv->con_closed++;
+ }
+
+ connection_reset(srv, con);
+
+ break;
+ case CON_STATE_CONNECT:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ chunkqueue_reset(con->read_queue);
+
+ con->request_count = 0;
+
+ break;
+ case CON_STATE_CLOSE:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ if (con->keep_alive) {
+ if (ioctl(con->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "ioctl() failed", strerror(errno));
+ }
+ if (b > 0) {
+ char buf[1024];
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "CLOSE-read()", con->fd, b);
+
+ /* */
+ read(con->fd, buf, sizeof(buf));
+ } else {
+ /* nothing to read */
+
+ con->close_timeout_ts = 0;
+ }
+ } else {
+ con->close_timeout_ts = 0;
+ }
+
+ if (srv->cur_ts - con->close_timeout_ts > 1) {
+ connection_close(srv, con);
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed for fd", con->fd);
+ }
+ }
+
+ break;
+ case CON_STATE_READ_POST:
+ case CON_STATE_READ:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ connection_handle_read_state(srv, con);
+ break;
+ case CON_STATE_WRITE:
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state for fd", con->fd, connection_get_state(con->state));
+ }
+
+ /* only try to write if we have something in the queue */
+ if (!chunkqueue_is_empty(con->write_queue)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "dsd",
+ con->fd,
+ "packets to write:",
+ con->write_queue->used);
+#endif
+ }
+ if (!chunkqueue_is_empty(con->write_queue) && con->is_writable) {
+ if (-1 == connection_handle_write(srv, con)) {
+ log_error_write(srv, __FILE__, __LINE__, "ds",
+ con->fd,
+ "handle write failed.");
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ } else if (con->state == CON_STATE_WRITE) {
+ con->write_request_ts = srv->cur_ts;
+ }
+ }
+
+ break;
+ case CON_STATE_ERROR: /* transient */
+
+ /* even if the connection was drop we still have to write it to the access log */
+ if (con->http_status) {
+ plugins_call_handle_request_done(srv, con);
+ }
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ int ret;
+ switch ((ret = SSL_shutdown(con->ssl))) {
+ case 1:
+ /* ok */
+ break;
+ case 0:
+ SSL_shutdown(con->ssl);
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
+ SSL_get_error(con->ssl, ret),
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+ }
+#endif
+
+ switch(con->mode) {
+ case DIRECT:
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "emergency exit: direct",
+ con->fd);
+#endif
+ break;
+ default:
+ switch(r = plugins_call_handle_connection_close(srv, con)) {
+ case HANDLER_GO_ON:
+ case HANDLER_FINISHED:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+ break;
+ }
+
+ connection_reset(srv, con);
+
+ /* close the connection */
+ if ((con->keep_alive == 1) &&
+ (0 == shutdown(con->fd, SHUT_WR))) {
+ con->close_timeout_ts = srv->cur_ts;
+ connection_set_state(srv, con, CON_STATE_CLOSE);
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "shutdown for fd", con->fd);
+ }
+ } else {
+ connection_close(srv, con);
+ }
+
+ con->keep_alive = 0;
+
+ srv->con_closed++;
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "unknown state:", con->fd, con->state);
+
+ break;
+ }
+
+ if (done == -1) {
+ done = 0;
+ } else if (ostate == con->state) {
+ done = 1;
+ }
+ }
+
+ if (srv->srvconf.log_state_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "state at exit:",
+ con->fd,
+ connection_get_state(con->state));
+ }
+
+ switch(con->state) {
+ case CON_STATE_READ_POST:
+ case CON_STATE_READ:
+ case CON_STATE_CLOSE:
+ fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
+ break;
+ case CON_STATE_WRITE:
+ /* request write-fdevent only if we really need it
+ * - if we have data to write
+ * - if the socket is not writable yet
+ */
+ if (!chunkqueue_is_empty(con->write_queue) &&
+ (con->is_writable == 0) &&
+ (con->traffic_limit_reached == 0)) {
+ fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
+ } else {
+ fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
+ }
+ break;
+ default:
+ fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/connections.h b/src/connections.h
new file mode 100644
index 00000000..1fcfc365
--- /dev/null
+++ b/src/connections.h
@@ -0,0 +1,19 @@
+#ifndef _CONNECTIONS_H_
+#define _CONNECTIONS_H_
+
+#include "server.h"
+#include "fdevent.h"
+
+connection *connection_init(server *srv);
+int connection_reset(server *srv, connection *con);
+void connections_free(server *srv);
+
+connection * connection_accept(server *srv, server_socket *srv_sock);
+int connection_close(server *srv, connection *con);
+
+int connection_set_state(server *srv, connection *con, connection_state_t state);
+const char * connection_get_state(connection_state_t state);
+const char * connection_get_short_state(connection_state_t state);
+int connection_state_machine(server *srv, connection *con);
+
+#endif
diff --git a/src/crc32.c b/src/crc32.c
new file mode 100644
index 00000000..2b2053e1
--- /dev/null
+++ b/src/crc32.c
@@ -0,0 +1,84 @@
+#include "crc32.h"
+
+#define CRC32C(c,d) (c=(c>>8)^crc_c[(c^(d))&0xFF])
+
+static const unsigned int crc_c[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+};
+
+
+unsigned long
+generate_crc32c(unsigned char *buffer, unsigned int length)
+{
+ unsigned int i;
+ unsigned long crc32 = ~0L;
+
+ for (i = 0; i < length; i++){
+ CRC32C(crc32, buffer[i]);
+ }
+ return ~crc32;
+}
+
diff --git a/src/crc32.h b/src/crc32.h
new file mode 100644
index 00000000..e5b1c2fd
--- /dev/null
+++ b/src/crc32.h
@@ -0,0 +1,6 @@
+#ifndef __crc32cr_table_h__
+#define __crc32cr_table_h__
+
+unsigned long generate_crc32c(unsigned char *string, unsigned int length);
+
+#endif
diff --git a/src/data_array.c b/src/data_array.c
new file mode 100644
index 00000000..6cd740c4
--- /dev/null
+++ b/src/data_array.c
@@ -0,0 +1,56 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static void data_array_free(data_unset *d) {
+ data_array *ds = (data_array *)d;
+
+ buffer_free(ds->key);
+ array_free(ds->value);
+
+ free(d);
+}
+
+static void data_array_reset(data_unset *d) {
+ data_array *ds = (data_array *)d;
+
+ /* reused array elements */
+ buffer_reset(ds->key);
+ array_reset(ds->value);
+}
+
+static int data_array_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_array_print(data_unset *d) {
+ data_array *ds = (data_array *)d;
+
+ printf("{%s:\n", ds->key->ptr);
+ array_print(ds->value);
+ printf("}");
+}
+
+
+data_array *data_array_init(void) {
+ data_array *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->value = array_init();
+
+ ds->free = data_array_free;
+ ds->reset = data_array_reset;
+ ds->insert_dup = data_array_insert_dup;
+ ds->print = data_array_print;
+ ds->type = TYPE_ARRAY;
+
+ return ds;
+}
diff --git a/src/data_config.c b/src/data_config.c
new file mode 100644
index 00000000..6a01af04
--- /dev/null
+++ b/src/data_config.c
@@ -0,0 +1,69 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static void data_config_free(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->comp_key);
+
+ array_free(ds->value);
+
+ switch(ds->cond) {
+ case CONFIG_COND_EQ: buffer_free(ds->match.string); break;
+#ifdef HAVE_PCRE_H
+ case CONFIG_COND_MATCH: pcre_free(ds->match.regex); break;
+#endif
+ default:
+ break;
+ }
+
+ free(d);
+}
+
+static void data_config_reset(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ /* reused array elements */
+ buffer_reset(ds->key);
+ buffer_reset(ds->comp_key);
+ array_reset(ds->value);
+}
+
+static int data_config_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_config_print(data_unset *d) {
+ data_config *ds = (data_config *)d;
+
+ printf("{%s:\n", ds->key->ptr);
+ array_print(ds->value);
+ printf("}");
+}
+
+
+data_config *data_config_init(void) {
+ data_config *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->comp_key = buffer_init();
+ ds->value = array_init();
+
+ ds->free = data_config_free;
+ ds->reset = data_config_reset;
+ ds->insert_dup = data_config_insert_dup;
+ ds->print = data_config_print;
+ ds->type = TYPE_CONFIG;
+
+ return ds;
+}
diff --git a/src/data_count.c b/src/data_count.c
new file mode 100644
index 00000000..843e3bb1
--- /dev/null
+++ b/src/data_count.c
@@ -0,0 +1,56 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+
+static void data_count_free(data_unset *d) {
+ data_count *ds = (data_count *)d;
+
+ buffer_free(ds->key);
+
+ free(d);
+}
+
+static void data_count_reset(data_unset *d) {
+ data_count *ds = (data_count *)d;
+
+ buffer_reset(ds->key);
+
+ ds->count = 0;
+}
+
+static int data_count_insert_dup(data_unset *dst, data_unset *src) {
+ data_count *ds_dst = (data_count *)dst;
+ data_count *ds_src = (data_count *)src;
+
+ ds_dst->count += ds_src->count;
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_count_print(data_unset *d) {
+ data_count *ds = (data_count *)d;
+
+ printf("{%s: %d}", ds->key->ptr, ds->count);
+}
+
+
+data_count *data_count_init(void) {
+ data_count *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->count = 1;
+
+ ds->free = data_count_free;
+ ds->reset = data_count_reset;
+ ds->insert_dup = data_count_insert_dup;
+ ds->print = data_count_print;
+ ds->type = TYPE_COUNT;
+
+ return ds;
+}
diff --git a/src/data_fastcgi.c b/src/data_fastcgi.c
new file mode 100644
index 00000000..28273752
--- /dev/null
+++ b/src/data_fastcgi.c
@@ -0,0 +1,56 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "array.h"
+#include "fastcgi.h"
+
+static void data_fastcgi_free(data_unset *d) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->host);
+
+ free(d);
+}
+
+static void data_fastcgi_reset(data_unset *d) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+
+ buffer_reset(ds->key);
+ buffer_reset(ds->host);
+
+}
+
+static int data_fastcgi_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_fastcgi_print(data_unset *d) {
+ data_fastcgi *ds = (data_fastcgi *)d;
+
+ printf("{%s: %s}", ds->key->ptr, ds->host->ptr);
+}
+
+
+data_fastcgi *data_fastcgi_init(void) {
+ data_fastcgi *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->host = buffer_init();
+ ds->port = 0;
+
+ ds->free = data_fastcgi_free;
+ ds->reset = data_fastcgi_reset;
+ ds->insert_dup = data_fastcgi_insert_dup;
+ ds->print = data_fastcgi_print;
+ ds->type = TYPE_FASTCGI;
+
+ return ds;
+}
diff --git a/src/data_integer.c b/src/data_integer.c
new file mode 100644
index 00000000..af6ad221
--- /dev/null
+++ b/src/data_integer.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "array.h"
+
+static void data_integer_free(data_unset *d) {
+ data_integer *ds = (data_integer *)d;
+
+ buffer_free(ds->key);
+
+ free(d);
+}
+
+static void data_integer_reset(data_unset *d) {
+ data_integer *ds = (data_integer *)d;
+
+ /* reused integer elements */
+ buffer_reset(ds->key);
+ ds->value = 0;
+}
+
+static int data_integer_insert_dup(data_unset *dst, data_unset *src) {
+ UNUSED(dst);
+
+ src->free(src);
+
+ return 0;
+}
+
+static void data_integer_print(data_unset *d) {
+ data_integer *ds = (data_integer *)d;
+
+ printf("{%s: %d}", ds->key->ptr, ds->value);
+}
+
+
+data_integer *data_integer_init(void) {
+ data_integer *ds;
+
+ ds = calloc(1, sizeof(*ds));
+
+ ds->key = buffer_init();
+ ds->value = 0;
+
+ ds->free = data_integer_free;
+ ds->reset = data_integer_reset;
+ ds->insert_dup = data_integer_insert_dup;
+ ds->print = data_integer_print;
+ ds->type = TYPE_INTEGER;
+
+ return ds;
+}
diff --git a/src/data_string.c b/src/data_string.c
new file mode 100644
index 00000000..5ce160a3
--- /dev/null
+++ b/src/data_string.c
@@ -0,0 +1,92 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "array.h"
+
+static void data_string_free(data_unset *d) {
+ data_string *ds = (data_string *)d;
+
+ buffer_free(ds->key);
+ buffer_free(ds->value);
+
+ free(d);
+}
+
+static void data_string_reset(data_unset *d) {
+ data_string *ds = (data_string *)d;
+
+ /* reused array elements */
+ buffer_reset(ds->key);
+ buffer_reset(ds->value);
+}
+
+static int data_string_insert_dup(data_unset *dst, data_unset *src) {
+ data_string *ds_dst = (data_string *)dst;
+ data_string *ds_src = (data_string *)src;
+
+ if (ds_dst->value->used) {
+ buffer_append_string(ds_dst->value, ", ");
+ buffer_append_string_buffer(ds_dst->value, ds_src->value);
+ } else {
+ buffer_copy_string_buffer(ds_dst->value, ds_src->value);
+ }
+
+ src->free(src);
+
+ return 0;
+}
+
+static int data_response_insert_dup(data_unset *dst, data_unset *src) {
+ data_string *ds_dst = (data_string *)dst;
+ data_string *ds_src = (data_string *)src;
+
+ if (ds_dst->value->used) {
+ buffer_append_string(ds_dst->value, "\r\n");
+ buffer_append_string_buffer(ds_dst->value, ds_dst->key);
+ buffer_append_string(ds_dst->value, ": ");
+ buffer_append_string_buffer(ds_dst->value, ds_src->value);
+ } else {
+ buffer_copy_string_buffer(ds_dst->value, ds_src->value);
+ }
+
+ src->free(src);
+
+ return 0;
+}
+
+
+static void data_string_print(data_unset *d) {
+ data_string *ds = (data_string *)d;
+
+ fprintf(stderr, "{%s: %s}", ds->key->ptr, ds->value->used ? ds->value->ptr : "");
+}
+
+
+data_string *data_string_init(void) {
+ data_string *ds;
+
+ ds = calloc(1, sizeof(*ds));
+ assert(ds);
+
+ ds->key = buffer_init();
+ ds->value = buffer_init();
+
+ ds->free = data_string_free;
+ ds->reset = data_string_reset;
+ ds->insert_dup = data_string_insert_dup;
+ ds->print = data_string_print;
+ ds->type = TYPE_STRING;
+
+ return ds;
+}
+
+data_string *data_response_init(void) {
+ data_string *ds;
+
+ ds = data_string_init();
+ ds->insert_dup = data_response_insert_dup;
+
+ return ds;
+}
diff --git a/src/etag.c b/src/etag.c
new file mode 100644
index 00000000..eb1b2f41
--- /dev/null
+++ b/src/etag.c
@@ -0,0 +1,30 @@
+#include <string.h>
+
+#include "buffer.h"
+#include "etag.h"
+
+int etag_is_equal(buffer *etag, const char *matches) {
+ if (0 == strcmp(etag->ptr, matches)) return 1;
+ return 0;
+}
+
+int etag_create(buffer *etag, struct stat *st) {
+ buffer_copy_off_t(etag, st->st_ino);
+ buffer_append_string_len(etag, "-", 1);
+ buffer_append_off_t(etag, st->st_size);
+ buffer_append_string_len(etag, "-", 1);
+ buffer_append_long(etag, st->st_mtime);
+
+ return 0;
+}
+
+int etag_mutate(buffer *mut, buffer *etag) {
+ size_t h, i;
+
+ for (h=0, i=0; i < etag->used; ++i) h = (h<<5)^(h>>27)^(etag->ptr[i]);
+
+ buffer_reset(mut);
+ buffer_copy_long(mut, h);
+
+ return 0;
+}
diff --git a/src/etag.h b/src/etag.h
new file mode 100644
index 00000000..53fae00a
--- /dev/null
+++ b/src/etag.h
@@ -0,0 +1,15 @@
+#ifndef ETAG_H
+#define ETAG_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "buffer.h"
+
+int etag_is_equal(buffer *etag, const char *matches);
+int etag_create(buffer *etag, struct stat *st);
+int etag_mutate(buffer *mut, buffer *etag);
+
+
+#endif
diff --git a/src/fastcgi.h b/src/fastcgi.h
new file mode 100644
index 00000000..15f1deae
--- /dev/null
+++ b/src/fastcgi.h
@@ -0,0 +1,136 @@
+/*
+ * fastcgi.h --
+ *
+ * Defines for the FastCGI protocol.
+ *
+ *
+ * Copyright (c) 1995-1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: fastcgi.h,v 1.1.1.1 2003/10/18 09:54:10 weigon Exp $
+ */
+
+#ifndef _FASTCGI_H
+#define _FASTCGI_H
+
+/*
+ * Listening socket file number
+ */
+#define FCGI_LISTENSOCK_FILENO 0
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+} FCGI_Header;
+
+#define FCGI_MAX_LENGTH 0xffff
+
+/*
+ * Number of bytes in a FCGI_Header. Future versions of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN 8
+
+/*
+ * Value for version component of FCGI_Header
+ */
+#define FCGI_VERSION_1 1
+
+/*
+ * Values for type component of FCGI_Header
+ */
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+/*
+ * Value for requestId component of FCGI_Header
+ */
+#define FCGI_NULL_REQUEST_ID 0
+
+
+typedef struct {
+ unsigned char roleB1;
+ unsigned char roleB0;
+ unsigned char flags;
+ unsigned char reserved[5];
+} FCGI_BeginRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_BeginRequestBody body;
+} FCGI_BeginRequestRecord;
+
+/*
+ * Mask for flags component of FCGI_BeginRequestBody
+ */
+#define FCGI_KEEP_CONN 1
+
+/*
+ * Values for role component of FCGI_BeginRequestBody
+ */
+#define FCGI_RESPONDER 1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER 3
+
+
+typedef struct {
+ unsigned char appStatusB3;
+ unsigned char appStatusB2;
+ unsigned char appStatusB1;
+ unsigned char appStatusB0;
+ unsigned char protocolStatus;
+ unsigned char reserved[3];
+} FCGI_EndRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_EndRequestBody body;
+} FCGI_EndRequestRecord;
+
+/*
+ * Values for protocolStatus component of FCGI_EndRequestBody
+ */
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN 1
+#define FCGI_OVERLOADED 2
+#define FCGI_UNKNOWN_ROLE 3
+
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
+ */
+#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS "FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
+
+
+typedef struct {
+ unsigned char type;
+ unsigned char reserved[7];
+} FCGI_UnknownTypeBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_UnknownTypeBody body;
+} FCGI_UnknownTypeRecord;
+
+#endif /* _FASTCGI_H */
+
diff --git a/src/fdevent.c b/src/fdevent.c
new file mode 100644
index 00000000..7deda395
--- /dev/null
+++ b/src/fdevent.c
@@ -0,0 +1,202 @@
+#include <sys/types.h>
+
+#include "settings.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "buffer.h"
+
+fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type) {
+ fdevents *ev;
+
+ ev = calloc(1, sizeof(*ev));
+ ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray));
+ ev->maxfds = maxfds;
+
+ switch(type) {
+ case FDEVENT_HANDLER_POLL:
+ if (0 != fdevent_poll_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler poll failed\n",
+ __FILE__, __LINE__);
+
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_SELECT:
+ if (0 != fdevent_select_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler select failed\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_LINUX_RTSIG:
+ if (0 != fdevent_linux_rtsig_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_LINUX_SYSEPOLL:
+ if (0 != fdevent_linux_sysepoll_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_SOLARIS_DEVPOLL:
+ if (0 != fdevent_solaris_devpoll_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ case FDEVENT_HANDLER_FREEBSD_KQUEUE:
+ if (0 != fdevent_freebsd_kqueue_init(ev)) {
+ fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+ break;
+ default:
+ fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return NULL;
+ }
+
+ return ev;
+}
+
+void fdevent_free(fdevents *ev) {
+ size_t i;
+ if (!ev) return;
+
+ if (ev->free) ev->free(ev);
+
+ for (i = 0; i < ev->maxfds; i++) {
+ if (ev->fdarray[i]) free(ev->fdarray[i]);
+ }
+
+ free(ev->fdarray);
+ free(ev);
+}
+
+int fdevent_reset(fdevents *ev) {
+ if (ev->reset) return ev->reset(ev);
+
+ return 0;
+}
+
+fdnode *fdnode_init() {
+ fdnode *fdn;
+
+ fdn = calloc(1, sizeof(*fdn));
+ fdn->fd = -1;
+ return fdn;
+}
+
+void fdnode_free(fdnode *fdn) {
+ free(fdn);
+}
+
+int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx) {
+ fdnode *fdn;
+
+ fdn = fdnode_init();
+ fdn->handler = handler;
+ fdn->fd = fd;
+ fdn->ctx = ctx;
+
+ ev->fdarray[fd] = fdn;
+
+ return 0;
+}
+
+int fdevent_unregister(fdevents *ev, int fd) {
+ fdnode *fdn;
+ if (!ev) return 0;
+ fdn = ev->fdarray[fd];
+
+ fdnode_free(fdn);
+
+ ev->fdarray[fd] = NULL;
+
+ return 0;
+}
+
+int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd) {
+ int fde = fde_ndx ? *fde_ndx : -1;
+
+ if (ev->event_del) fde = ev->event_del(ev, fde, fd);
+
+ if (fde_ndx) *fde_ndx = fde;
+
+ return 0;
+}
+
+int fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int events) {
+ int fde = fde_ndx ? *fde_ndx : -1;
+
+ if (ev->event_add) fde = ev->event_add(ev, fde, fd, events);
+
+ if (fde_ndx) *fde_ndx = fde;
+
+ return 0;
+}
+
+int fdevent_poll(fdevents *ev, int timeout_ms) {
+ if (ev->poll == NULL) SEGFAULT();
+ return ev->poll(ev, timeout_ms);
+}
+
+int fdevent_event_get_revent(fdevents *ev, size_t ndx) {
+ if (ev->event_get_revent == NULL) SEGFAULT();
+
+ return ev->event_get_revent(ev, ndx);
+}
+
+int fdevent_event_get_fd(fdevents *ev, size_t ndx) {
+ if (ev->event_get_fd == NULL) SEGFAULT();
+
+ return ev->event_get_fd(ev, ndx);
+}
+
+fdevent_handler fdevent_get_handler(fdevents *ev, int fd) {
+ if (ev->fdarray[fd] == NULL) SEGFAULT();
+ if (ev->fdarray[fd]->fd != fd) SEGFAULT();
+
+ return ev->fdarray[fd]->handler;
+}
+
+void * fdevent_get_context(fdevents *ev, int fd) {
+ if (ev->fdarray[fd] == NULL) SEGFAULT();
+ if (ev->fdarray[fd]->fd != fd) SEGFAULT();
+
+ return ev->fdarray[fd]->ctx;
+}
+
+int fdevent_fcntl_set(fdevents *ev, int fd) {
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+ if (ev->fcntl_set) return ev->fcntl_set(ev, fd);
+#ifdef O_NONBLOCK
+ return fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#else
+ return 0;
+#endif
+}
+
+
+int fdevent_event_next_fdndx(fdevents *ev, int ndx) {
+ if (ev->event_next_fdndx) return ev->event_next_fdndx(ev, ndx);
+
+ return -1;
+}
+
diff --git a/src/fdevent.h b/src/fdevent.h
new file mode 100644
index 00000000..5197713b
--- /dev/null
+++ b/src/fdevent.h
@@ -0,0 +1,222 @@
+#ifndef _FDEVENT_H_
+#define _FDEVENT_H_
+
+#include "config.h"
+#include "settings.h"
+#include "bitset.h"
+
+/* select event-system */
+
+#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H)
+# if defined HAVE_STDINT_H
+# include <stdint.h>
+# endif
+# define USE_LINUX_EPOLL
+# include <sys/epoll.h>
+#endif
+
+/* MacOS 10.3.x has poll.h under /usr/include/, all other unixes
+ * under /usr/include/sys/ */
+#if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H))
+# define USE_POLL
+# ifdef HAVE_POLL_H
+# include <poll.h>
+# else
+# include <sys/poll.h>
+# endif
+# if defined HAVE_SIGTIMEDWAIT && defined(__linux__)
+# define USE_LINUX_SIGIO
+# include <signal.h>
+# endif
+#endif
+
+#if defined HAVE_SELECT
+# ifdef __WIN32
+# include <winsock2.h>
+# endif
+# define USE_SELECT
+#endif
+
+#if defined HAVE_SYS_DEVPOLL_H && defined(__sun)
+# define USE_SOLARIS_DEVPOLL
+# include <sys/devpoll.h>
+#endif
+
+#if defined HAVE_SYS_EVENT_H && defined HAVE_KQUEUE
+# define USE_FREEBSD_KQUEUE
+# include <sys/event.h>
+#endif
+
+#if defined HAVE_SYS_PORT_H && defined HAVE_PORT_CREATE
+# define USE_SOLARIS_PORT
+# include <sys/port.h>
+#endif
+
+
+typedef handler_t (*fdevent_handler)(void *srv, void *ctx, int revents);
+
+#define FDEVENT_IN BV(0)
+#define FDEVENT_PRI BV(1)
+#define FDEVENT_OUT BV(2)
+#define FDEVENT_ERR BV(3)
+#define FDEVENT_HUP BV(4)
+#define FDEVENT_NVAL BV(5)
+
+typedef enum { FD_EVENT_TYPE_UNSET = -1,
+ FD_EVENT_TYPE_CONNECTION,
+ FD_EVENT_TYPE_FCGI_CONNECTION,
+ FD_EVENT_TYPE_DIRWATCH,
+ FD_EVENT_TYPE_CGI_CONNECTION
+} fd_event_t;
+
+typedef enum { FDEVENT_HANDLER_UNSET,
+ FDEVENT_HANDLER_SELECT,
+ FDEVENT_HANDLER_POLL,
+ FDEVENT_HANDLER_LINUX_RTSIG,
+ FDEVENT_HANDLER_LINUX_SYSEPOLL,
+ FDEVENT_HANDLER_SOLARIS_DEVPOLL,
+ FDEVENT_HANDLER_FREEBSD_KQUEUE,
+ FDEVENT_HANDLER_SOLARIS_PORT
+} fdevent_handler_t;
+
+/**
+ * a mapping from fd to connection structure
+ *
+ */
+typedef struct {
+ int fd; /**< the fd */
+ void *conn; /**< a reference the corresponding data-structure */
+ fd_event_t fd_type; /**< type of the fd */
+ int events; /**< registered events */
+ int revents;
+} fd_conn;
+
+typedef struct {
+ fd_conn *ptr;
+
+ size_t size;
+ size_t used;
+} fd_conn_buffer;
+
+/**
+ * array of unused fd's
+ *
+ */
+
+typedef struct fdnode {
+ fdevent_handler handler;
+ void *ctx;
+ int fd;
+
+ struct fdnode *prev, *next;
+} fdnode;
+
+typedef struct {
+ fdnode *first, *last;
+} fdlist;
+
+typedef struct {
+ int *ptr;
+
+ size_t used;
+ size_t size;
+} buffer_int;
+
+/**
+ * fd-event handler for select(), poll() and rt-signals on Linux 2.4
+ *
+ */
+typedef struct fdevents {
+ fdevent_handler_t type;
+
+ fdlist fdlist;
+ fdnode **fdarray;
+ size_t maxfds;
+
+#ifdef USE_LINUX_SIGIO
+ int in_sigio;
+ int signum;
+ sigset_t sigset;
+ siginfo_t siginfo;
+ bitset *sigbset;
+#endif
+#ifdef USE_LINUX_EPOLL
+ int epoll_fd;
+ struct epoll_event *epoll_events;
+#endif
+#ifdef USE_POLL
+ struct pollfd *pollfds;
+
+ size_t size;
+ size_t used;
+
+ buffer_int unused;
+#endif
+#ifdef USE_SELECT
+ fd_set select_read;
+ fd_set select_write;
+ fd_set select_error;
+
+ fd_set select_set_read;
+ fd_set select_set_write;
+ fd_set select_set_error;
+
+ int select_max_fd;
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ int devpoll_fd;
+ struct pollfd *devpollfds;
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ int kq_fd;
+ struct kevent *kq_results;
+ bitset *kq_bevents;
+#endif
+#ifdef USE_SOLARIS_PORT
+ int port_fd;
+#endif
+ int (*reset)(struct fdevents *ev);
+ void (*free)(struct fdevents *ev);
+
+ int (*event_add)(struct fdevents *ev, int fde_ndx, int fd, int events);
+ int (*event_del)(struct fdevents *ev, int fde_ndx, int fd);
+ int (*event_get_revent)(struct fdevents *ev, size_t ndx);
+ int (*event_get_fd)(struct fdevents *ev, size_t ndx);
+
+ int (*event_next_fdndx)(struct fdevents *ev, int ndx);
+
+ int (*poll)(struct fdevents *ev, int timeout_ms);
+
+ int (*fcntl_set)(struct fdevents *ev, int fd);
+} fdevents;
+
+fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type);
+int fdevent_reset(fdevents *ev);
+void fdevent_free(fdevents *ev);
+
+int fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int events);
+int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd);
+int fdevent_event_get_revent(fdevents *ev, size_t ndx);
+int fdevent_event_get_fd(fdevents *ev, size_t ndx);
+fdevent_handler fdevent_get_handler(fdevents *ev, int fd);
+void * fdevent_get_context(fdevents *ev, int fd);
+
+int fdevent_event_next_fdndx(fdevents *ev, int ndx);
+
+int fdevent_poll(fdevents *ev, int timeout_ms);
+
+int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx);
+int fdevent_unregister(fdevents *ev, int fd);
+
+int fdevent_fcntl_set(fdevents *ev, int fd);
+
+int fdevent_select_init(fdevents *ev);
+int fdevent_poll_init(fdevents *ev);
+int fdevent_linux_rtsig_init(fdevents *ev);
+int fdevent_linux_sysepoll_init(fdevents *ev);
+int fdevent_solaris_devpoll_init(fdevents *ev);
+int fdevent_freebsd_kqueue_init(fdevents *ev);
+
+#endif
+
+
diff --git a/src/fdevent_freebsd_kqueue.c b/src/fdevent_freebsd_kqueue.c
new file mode 100644
index 00000000..0585cfbf
--- /dev/null
+++ b/src/fdevent_freebsd_kqueue.c
@@ -0,0 +1,194 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+#include "server.h"
+
+#ifdef USE_FREEBSD_KQUEUE
+#include <sys/event.h>
+#include <sys/time.h>
+
+static void fdevent_freebsd_kqueue_free(fdevents *ev) {
+ close(ev->kq_fd);
+ free(ev->kq_results);
+ bitset_free(ev->kq_bevents);
+}
+
+static int fdevent_freebsd_kqueue_event_del(fdevents *ev, int fde_ndx, int fd) {
+ int filter, ret;
+ struct kevent kev;
+ struct timespec ts;
+
+ if (fde_ndx < 0) return -1;
+
+ filter = bitset_test_bit(ev->kq_bevents, fd) ? EVFILT_READ : EVFILT_WRITE;
+
+ EV_SET(&kev, fd, filter, EV_DELETE, 0, 0, NULL);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ ret = kevent(ev->kq_fd,
+ &kev, 1,
+ NULL, 0,
+ &ts);
+
+ if (ret == -1) {
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ return -1;
+}
+
+static int fdevent_freebsd_kqueue_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ int filter, ret;
+ struct kevent kev;
+ struct timespec ts;
+
+ UNUSED(fde_ndx);
+
+ filter = (events & FDEVENT_IN) ? EVFILT_READ : EVFILT_WRITE;
+
+ EV_SET(&kev, fd, filter, EV_ADD|EV_CLEAR, 0, 0, NULL);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ ret = kevent(ev->kq_fd,
+ &kev, 1,
+ NULL, 0,
+ &ts);
+
+ if (ret == -1) {
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ if (filter == EVFILT_READ) {
+ bitset_set_bit(ev->kq_bevents, fd);
+ } else {
+ bitset_clear_bit(ev->kq_bevents, fd);
+ }
+
+ return fd;
+}
+
+static int fdevent_freebsd_kqueue_poll(fdevents *ev, int timeout_ms) {
+ int ret;
+ struct timespec ts;
+
+ ts.tv_sec = timeout_ms / 1000;
+ ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+
+ ret = kevent(ev->kq_fd,
+ NULL, 0,
+ ev->kq_results, ev->maxfds,
+ &ts);
+
+ if (ret == -1) {
+ fprintf(stderr, "%s.%d: kqueue failed polling: %s\n",
+ __FILE__, __LINE__, strerror(errno));
+ }
+
+ return ret;
+}
+
+static int fdevent_freebsd_kqueue_event_get_revent(fdevents *ev, size_t ndx) {
+ int events = 0, e;
+
+ e = ev->kq_results[ndx].filter;
+
+ if (e == EVFILT_READ) {
+ events |= FDEVENT_IN;
+ } else if (e == EVFILT_WRITE) {
+ events |= FDEVENT_OUT;
+ }
+
+ e = ev->kq_results[ndx].flags;
+
+ if (e & EV_EOF) {
+ events |= FDEVENT_HUP;
+ }
+
+ if (e & EV_ERROR) {
+ events |= FDEVENT_ERR;
+ }
+
+ return events;
+}
+
+static int fdevent_freebsd_kqueue_event_get_fd(fdevents *ev, size_t ndx) {
+ return ev->kq_results[ndx].ident;
+}
+
+static int fdevent_freebsd_kqueue_event_next_fdndx(fdevents *ev, int ndx) {
+ UNUSED(ev);
+
+ return (ndx < 0) ? 0 : ndx + 1;
+}
+
+static int fdevent_freebsd_kqueue_reset(fdevents *ev) {
+ close(ev->kq_fd);
+
+ if (-1 == (ev->kq_fd = kqueue())) {
+ fprintf(stderr, "%s.%d: kqueue failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int fdevent_freebsd_kqueue_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_FREEBSD_KQUEUE;
+#define SET(x) \
+ ev->x = fdevent_freebsd_kqueue_##x;
+
+ SET(free);
+ SET(poll);
+ SET(reset);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ if (-1 == (ev->kq_fd = kqueue())) {
+ fprintf(stderr, "%s.%d: kqueue failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+ ev->kq_results = calloc(ev->maxfds, sizeof(*ev->kq_results));
+ ev->kq_bevents = bitset_init(ev->maxfds);
+
+ return 0;
+}
+#else
+int fdevent_freebsd_kqueue_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: kqueue not available, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+
+ return -1;
+}
+#endif
diff --git a/src/fdevent_linux_rtsig.c b/src/fdevent_linux_rtsig.c
new file mode 100644
index 00000000..8bedaf0d
--- /dev/null
+++ b/src/fdevent_linux_rtsig.c
@@ -0,0 +1,260 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <limits.h>
+
+#define __USE_GNU
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_LINUX_SIGIO
+static void fdevent_linux_rtsig_free(fdevents *ev) {
+ free(ev->pollfds);
+ if (ev->unused.ptr) free(ev->unused.ptr);
+
+ bitset_free(ev->sigbset);
+}
+
+
+static int fdevent_linux_rtsig_event_del(fdevents *ev, int fde_ndx, int fd) {
+ if (fde_ndx < 0) return -1;
+
+ if ((size_t)fde_ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: del! out of range %d %u\n", __FILE__, __LINE__, fde_ndx, ev->used);
+ SEGFAULT();
+ }
+
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ size_t k = fde_ndx;
+
+ ev->pollfds[k].fd = -1;
+
+ bitset_clear_bit(ev->sigbset, fd);
+
+ if (ev->unused.size == 0) {
+ ev->unused.size = 16;
+ ev->unused.ptr = malloc(sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ } else if (ev->unused.size == ev->unused.used) {
+ ev->unused.size += 16;
+ ev->unused.ptr = realloc(ev->unused.ptr, sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ }
+
+ ev->unused.ptr[ev->unused.used++] = k;
+ } else {
+ fprintf(stderr, "%s.%d: del! %d %d\n", __FILE__, __LINE__, ev->pollfds[fde_ndx].fd, fd);
+
+ SEGFAULT();
+ }
+
+ return -1;
+}
+
+#if 0
+static int fdevent_linux_rtsig_event_compress(fdevents *ev) {
+ size_t j;
+
+ if (ev->used == 0) return 0;
+ if (ev->unused.used != 0) return 0;
+
+ for (j = ev->used - 1; j + 1 > 0; j--) {
+ if (ev->pollfds[j].fd == -1) ev->used--;
+ }
+
+
+ return 0;
+}
+#endif
+
+static int fdevent_linux_rtsig_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ /* known index */
+ if (fde_ndx != -1) {
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ ev->pollfds[fde_ndx].events = events;
+
+ return fde_ndx;
+ }
+ fprintf(stderr, "%s.%d: add: (%d, %d)\n", __FILE__, __LINE__, fde_ndx, ev->pollfds[fde_ndx].fd);
+ SEGFAULT();
+ }
+
+ if (ev->unused.used > 0) {
+ int k = ev->unused.ptr[--ev->unused.used];
+
+ ev->pollfds[k].fd = fd;
+ ev->pollfds[k].events = events;
+
+ bitset_set_bit(ev->sigbset, fd);
+
+ return k;
+ } else {
+ if (ev->size == 0) {
+ ev->size = 16;
+ ev->pollfds = malloc(sizeof(*ev->pollfds) * ev->size);
+ } else if (ev->size == ev->used) {
+ ev->size += 16;
+ ev->pollfds = realloc(ev->pollfds, sizeof(*ev->pollfds) * ev->size);
+ }
+
+ ev->pollfds[ev->used].fd = fd;
+ ev->pollfds[ev->used].events = events;
+
+ bitset_set_bit(ev->sigbset, fd);
+
+ return ev->used++;
+ }
+}
+
+static int fdevent_linux_rtsig_poll(fdevents *ev, int timeout_ms) {
+ struct timespec ts;
+ int r;
+
+#if 0
+ fdevent_linux_rtsig_event_compress(ev);
+#endif
+
+ ev->in_sigio = 1;
+
+ ts.tv_sec = timeout_ms / 1000;
+ ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+ r = sigtimedwait(&(ev->sigset), &(ev->siginfo), &(ts));
+
+ if (r == -1) {
+ if (errno == EAGAIN) return 0;
+ return r;
+ } else if (r == SIGIO) {
+ struct sigaction act;
+
+ /* flush the signal queue */
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+ sigaction(ev->signum, &act, NULL);
+
+ /* re-enable the signal queue */
+ act.sa_handler = SIG_DFL;
+ sigaction(ev->signum, &act, NULL);
+
+ ev->in_sigio = 0;
+ r = poll(ev->pollfds, ev->used, timeout_ms);
+
+ return r;
+ } else if (r == ev->signum) {
+# if 0
+ fprintf(stderr, "event: %d %02lx\n", ev->siginfo.si_fd, ev->siginfo.si_band);
+# endif
+ return bitset_test_bit(ev->sigbset, ev->siginfo.si_fd);
+ } else {
+ /* ? */
+ return -1;
+ }
+}
+
+static int fdevent_linux_rtsig_event_get_revent(fdevents *ev, size_t ndx) {
+ if (ev->in_sigio == 1) {
+# if 0
+ if (ev->siginfo.si_band == POLLERR) {
+ fprintf(stderr, "event: %d %02lx %02x %s\n", ev->siginfo.si_fd, ev->siginfo.si_band, errno, strerror(errno));
+ }
+# endif
+ if (ndx != 0) {
+ fprintf(stderr, "+\n");
+ return 0;
+ }
+
+ return ev->siginfo.si_band & 0x3f;
+ } else {
+ if (ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: event: %u %u\n", __FILE__, __LINE__, ndx, ev->used);
+ return 0;
+ }
+ return ev->pollfds[ndx].revents;
+ }
+}
+
+static int fdevent_linux_rtsig_event_get_fd(fdevents *ev, size_t ndx) {
+ if (ev->in_sigio == 1) {
+ return ev->siginfo.si_fd;
+ } else {
+ return ev->pollfds[ndx].fd;
+ }
+}
+
+static int fdevent_linux_rtsig_fcntl_set(fdevents *ev, int fd) {
+ static pid_t pid = 0;
+
+ if (pid == 0) pid = getpid();
+
+ if (-1 == fcntl(fd, F_SETSIG, ev->signum)) return -1;
+
+ if (-1 == fcntl(fd, F_SETOWN, (int) pid)) return -1;
+
+ return fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK | O_RDWR);
+}
+
+
+static int fdevent_linux_rtsig_event_next_fdndx(fdevents *ev, int ndx) {
+ if (ev->in_sigio == 1) {
+ if (ndx < 0) return 0;
+ return -1;
+ } else {
+ size_t i;
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+ for (; i < ev->used; i++) {
+ if (ev->pollfds[i].revents) break;
+ }
+
+ return i;
+ }
+}
+
+int fdevent_linux_rtsig_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_LINUX_RTSIG;
+#define SET(x) \
+ ev->x = fdevent_linux_rtsig_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(fcntl_set);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ ev->signum = SIGRTMIN + 1;
+
+ sigemptyset(&(ev->sigset));
+ sigaddset(&(ev->sigset), ev->signum);
+ sigaddset(&(ev->sigset), SIGIO);
+ if (-1 == sigprocmask(SIG_BLOCK, &(ev->sigset), NULL)) {
+ fprintf(stderr, "%s.%d: sigprocmask failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ ev->in_sigio = 1;
+
+ ev->sigbset = bitset_init(ev->maxfds);
+
+ return 0;
+}
+#else
+int fdevent_linux_rtsig_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: linux-rtsig not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+ return -1;
+}
+#endif
diff --git a/src/fdevent_linux_sysepoll.c b/src/fdevent_linux_sysepoll.c
new file mode 100644
index 00000000..4175fc65
--- /dev/null
+++ b/src/fdevent_linux_sysepoll.c
@@ -0,0 +1,152 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_LINUX_EPOLL
+static void fdevent_linux_sysepoll_free(fdevents *ev) {
+ close(ev->epoll_fd);
+ free(ev->epoll_events);
+}
+
+static int fdevent_linux_sysepoll_event_del(fdevents *ev, int fde_ndx, int fd) {
+ struct epoll_event ep;
+
+ if (fde_ndx < 0) return -1;
+
+ memset(&ep, 0, sizeof(ep));
+
+ ep.data.fd = fd;
+ ep.data.ptr = NULL;
+
+ if (0 != epoll_ctl(ev->epoll_fd, EPOLL_CTL_DEL, fd, &ep)) {
+ fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno));
+
+ SEGFAULT();
+
+ return 0;
+ }
+
+
+ return -1;
+}
+
+static int fdevent_linux_sysepoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ struct epoll_event ep;
+ int add = 0;
+
+ if (fde_ndx == -1) add = 1;
+
+ memset(&ep, 0, sizeof(ep));
+
+ ep.events = 0;
+
+ if (events & FDEVENT_IN) ep.events |= EPOLLIN;
+ if (events & FDEVENT_OUT) ep.events |= EPOLLOUT;
+
+ ep.events |= EPOLLERR | EPOLLHUP | EPOLLET;
+
+ ep.data.ptr = NULL;
+ ep.data.fd = fd;
+
+ if (0 != epoll_ctl(ev->epoll_fd, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ep)) {
+ fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno));
+
+ SEGFAULT();
+
+ return 0;
+ }
+
+ return fd;
+}
+
+static int fdevent_linux_sysepoll_poll(fdevents *ev, int timeout_ms) {
+ return epoll_wait(ev->epoll_fd, ev->epoll_events, ev->maxfds, timeout_ms);
+}
+
+static int fdevent_linux_sysepoll_event_get_revent(fdevents *ev, size_t ndx) {
+ int events = 0, e;
+
+ e = ev->epoll_events[ndx].events;
+ if (e & EPOLLIN) events |= FDEVENT_IN;
+ if (e & EPOLLOUT) events |= FDEVENT_OUT;
+ if (e & EPOLLERR) events |= FDEVENT_ERR;
+ if (e & EPOLLHUP) events |= FDEVENT_HUP;
+ if (e & EPOLLPRI) events |= FDEVENT_PRI;
+
+ return e;
+}
+
+static int fdevent_linux_sysepoll_event_get_fd(fdevents *ev, size_t ndx) {
+# if 0
+ fprintf(stderr, "%s.%d: %d, %d\n", __FILE__, __LINE__, ndx, ev->epoll_events[ndx].data.fd);
+# endif
+
+ return ev->epoll_events[ndx].data.fd;
+}
+
+static int fdevent_linux_sysepoll_event_next_fdndx(fdevents *ev, int ndx) {
+ size_t i;
+
+ UNUSED(ev);
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+
+ return i;
+}
+
+int fdevent_linux_sysepoll_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
+#define SET(x) \
+ ev->x = fdevent_linux_sysepoll_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds))) {
+ fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC)) {
+ fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ close(ev->epoll_fd);
+
+ return -1;
+ }
+
+ ev->epoll_events = malloc(ev->maxfds * sizeof(*ev->epoll_events));
+
+ return 0;
+}
+
+#else
+int fdevent_linux_sysepoll_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: linux-sysepoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+
+ return -1;
+}
+#endif
diff --git a/src/fdevent_poll.c b/src/fdevent_poll.c
new file mode 100644
index 00000000..07a67a2f
--- /dev/null
+++ b/src/fdevent_poll.c
@@ -0,0 +1,182 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_POLL
+static void fdevent_poll_free(fdevents *ev) {
+ free(ev->pollfds);
+ if (ev->unused.ptr) free(ev->unused.ptr);
+}
+
+static int fdevent_poll_event_del(fdevents *ev, int fde_ndx, int fd) {
+ if (fde_ndx < 0) return -1;
+
+ if ((size_t)fde_ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: del! out of range %d %u\n", __FILE__, __LINE__, fde_ndx, ev->used);
+ SEGFAULT();
+ }
+
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ size_t k = fde_ndx;
+
+ ev->pollfds[k].fd = -1;
+ /* ev->pollfds[k].events = 0; */
+ /* ev->pollfds[k].revents = 0; */
+
+ if (ev->unused.size == 0) {
+ ev->unused.size = 16;
+ ev->unused.ptr = malloc(sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ } else if (ev->unused.size == ev->unused.used) {
+ ev->unused.size += 16;
+ ev->unused.ptr = realloc(ev->unused.ptr, sizeof(*(ev->unused.ptr)) * ev->unused.size);
+ }
+
+ ev->unused.ptr[ev->unused.used++] = k;
+ } else {
+ SEGFAULT();
+ }
+
+ return -1;
+}
+
+#if 0
+static int fdevent_poll_event_compress(fdevents *ev) {
+ size_t j;
+
+ if (ev->used == 0) return 0;
+ if (ev->unused.used != 0) return 0;
+
+ for (j = ev->used - 1; j + 1 > 0 && ev->pollfds[j].fd == -1; j--) ev->used--;
+
+ return 0;
+}
+#endif
+
+static int fdevent_poll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ /* known index */
+
+ if (fde_ndx != -1) {
+ if (ev->pollfds[fde_ndx].fd == fd) {
+ ev->pollfds[fde_ndx].events = events;
+
+ return fde_ndx;
+ }
+ fprintf(stderr, "%s.%d: add: (%d, %d)\n", __FILE__, __LINE__, fde_ndx, ev->pollfds[fde_ndx].fd);
+ SEGFAULT();
+ }
+
+ if (ev->unused.used > 0) {
+ int k = ev->unused.ptr[--ev->unused.used];
+
+ ev->pollfds[k].fd = fd;
+ ev->pollfds[k].events = events;
+
+ return k;
+ } else {
+ if (ev->size == 0) {
+ ev->size = 16;
+ ev->pollfds = malloc(sizeof(*ev->pollfds) * ev->size);
+ } else if (ev->size == ev->used) {
+ ev->size += 16;
+ ev->pollfds = realloc(ev->pollfds, sizeof(*ev->pollfds) * ev->size);
+ }
+
+ ev->pollfds[ev->used].fd = fd;
+ ev->pollfds[ev->used].events = events;
+
+ return ev->used++;
+ }
+}
+
+static int fdevent_poll_poll(fdevents *ev, int timeout_ms) {
+#if 0
+ fdevent_poll_event_compress(ev);
+#endif
+ return poll(ev->pollfds, ev->used, timeout_ms);
+}
+
+static int fdevent_poll_event_get_revent(fdevents *ev, size_t ndx) {
+ int r, poll_r;
+ if (ndx >= ev->used) {
+ fprintf(stderr, "%s.%d: dying because: event: %u >= %u\n", __FILE__, __LINE__, ndx, ev->used);
+
+ SEGFAULT();
+
+ return 0;
+ }
+
+ if (ev->pollfds[ndx].revents & POLLNVAL) {
+ /* should never happen */
+ SEGFAULT();
+ }
+
+ r = 0;
+ poll_r = ev->pollfds[ndx].revents;
+
+ /* map POLL* to FDEVEN_* */
+
+ if (poll_r & POLLIN) r |= FDEVENT_IN;
+ if (poll_r & POLLOUT) r |= FDEVENT_OUT;
+ if (poll_r & POLLERR) r |= FDEVENT_ERR;
+ if (poll_r & POLLHUP) r |= FDEVENT_HUP;
+ if (poll_r & POLLNVAL) r |= FDEVENT_NVAL;
+ if (poll_r & POLLPRI) r |= FDEVENT_PRI;
+
+ return ev->pollfds[ndx].revents;
+}
+
+static int fdevent_poll_event_get_fd(fdevents *ev, size_t ndx) {
+ return ev->pollfds[ndx].fd;
+}
+
+static int fdevent_poll_event_next_fdndx(fdevents *ev, int ndx) {
+ size_t i;
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+ for (; i < ev->used; i++) {
+ if (ev->pollfds[i].revents) break;
+ }
+
+ return i;
+}
+
+int fdevent_poll_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_POLL;
+#define SET(x) \
+ ev->x = fdevent_poll_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ return 0;
+}
+
+
+
+
+#else
+int fdevent_poll_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: poll is not supported, try to set server.event-handler = \"select\"\n",
+ __FILE__, __LINE__);
+ return -1;
+}
+#endif
diff --git a/src/fdevent_select.c b/src/fdevent_select.c
new file mode 100644
index 00000000..fe9d7236
--- /dev/null
+++ b/src/fdevent_select.c
@@ -0,0 +1,127 @@
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_SELECT
+
+static int fdevent_select_reset(fdevents *ev) {
+ FD_ZERO(&(ev->select_set_read));
+ FD_ZERO(&(ev->select_set_write));
+ FD_ZERO(&(ev->select_set_error));
+ ev->select_max_fd = -1;
+
+ return 0;
+}
+
+static int fdevent_select_event_del(fdevents *ev, int fde_ndx, int fd) {
+ if (fde_ndx < 0) return -1;
+
+ FD_CLR(fd, &(ev->select_set_read));
+ FD_CLR(fd, &(ev->select_set_write));
+ FD_CLR(fd, &(ev->select_set_error));
+
+ return -1;
+}
+
+static int fdevent_select_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ UNUSED(fde_ndx);
+
+ if (events & FDEVENT_IN) {
+ FD_SET(fd, &(ev->select_set_read));
+ FD_CLR(fd, &(ev->select_set_write));
+ }
+ if (events & FDEVENT_OUT) {
+ FD_CLR(fd, &(ev->select_set_read));
+ FD_SET(fd, &(ev->select_set_write));
+ }
+ FD_SET(fd, &(ev->select_set_error));
+
+ if (fd > ev->select_max_fd) ev->select_max_fd = fd;
+
+ return fd;
+}
+
+static int fdevent_select_poll(fdevents *ev, int timeout_ms) {
+ struct timeval tv;
+
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+
+ ev->select_read = ev->select_set_read;
+ ev->select_write = ev->select_set_write;
+ ev->select_error = ev->select_set_error;
+
+ return select(ev->select_max_fd + 1, &(ev->select_read), &(ev->select_write), &(ev->select_error), &tv);
+}
+
+static int fdevent_select_event_get_revent(fdevents *ev, size_t ndx) {
+ int revents = 0;
+
+ if (FD_ISSET(ndx, &(ev->select_read))) {
+ revents |= FDEVENT_IN;
+ }
+ if (FD_ISSET(ndx, &(ev->select_write))) {
+ revents |= FDEVENT_OUT;
+ }
+ if (FD_ISSET(ndx, &(ev->select_error))) {
+ revents |= FDEVENT_ERR;
+ }
+
+ return revents;
+}
+
+static int fdevent_select_event_get_fd(fdevents *ev, size_t ndx) {
+ UNUSED(ev);
+
+ return ndx;
+}
+
+static int fdevent_select_event_next_fdndx(fdevents *ev, int ndx) {
+ int i;
+
+ i = (ndx < 0) ? 0 : ndx + 1;
+
+ for (; i < ev->select_max_fd + 1; i++) {
+ if (FD_ISSET(i, &(ev->select_read))) break;
+ if (FD_ISSET(i, &(ev->select_write))) break;
+ if (FD_ISSET(i, &(ev->select_error))) break;
+ }
+
+ return i;
+}
+
+int fdevent_select_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_SELECT;
+#define SET(x) \
+ ev->x = fdevent_select_##x;
+
+ SET(reset);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ return 0;
+}
+
+#else
+int fdevent_select_init(fdevents *ev) {
+ UNUSED(ev);
+
+ return -1;
+}
+#endif
diff --git a/src/fdevent_solaris_devpoll.c b/src/fdevent_solaris_devpoll.c
new file mode 100644
index 00000000..7e6680c1
--- /dev/null
+++ b/src/fdevent_solaris_devpoll.c
@@ -0,0 +1,141 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "fdevent.h"
+#include "settings.h"
+#include "buffer.h"
+
+#ifdef USE_SOLARIS_DEVPOLL
+
+static void fdevent_solaris_devpoll_free(fdevents *ev) {
+ free(ev->devpollfds);
+ close(ev->devpoll_fd);
+}
+
+/* return -1 is fine here */
+
+static int fdevent_solaris_devpoll_event_del(fdevents *ev, int fde_ndx, int fd) {
+ struct pollfd pfd;
+
+ if (fde_ndx < 0) return -1;
+
+ pfd.fd = fd;
+ pfd.events = POLLREMOVE;
+ pfd.revents = 0;
+
+ if (-1 == write(ev->devpoll_fd, &pfd, sizeof(pfd))) {
+ fprintf(stderr, "%s.%d: (del) write failed: (%d, %s)\n",
+ __FILE__, __LINE__,
+ fd, strerror(errno));
+
+ return -1;
+ }
+
+ return -1;
+}
+
+static int fdevent_solaris_devpoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
+ struct pollfd pfd;
+ int add = 0;
+
+ if (fde_ndx == -1) add = 1;
+
+ pfd.fd = fd;
+ pfd.events = events;
+ pfd.revents = 0;
+
+ if (-1 == write(ev->devpoll_fd, &pfd, sizeof(pfd))) {
+ fprintf(stderr, "%s.%d: (del) write failed: (%d, %s)\n",
+ __FILE__, __LINE__,
+ fd, strerror(errno));
+
+ return -1;
+ }
+
+ return fd;
+}
+
+static int fdevent_solaris_devpoll_poll(fdevents *ev, int timeout_ms) {
+ struct dvpoll dopoll;
+ int ret;
+
+ dopoll.dp_timeout = timeout_ms;
+ dopoll.dp_nfds = ev->maxfds;
+ dopoll.dp_fds = ev->devpollfds;
+
+ ret = ioctl(ev->devpoll_fd, DP_POLL, &dopoll);
+
+ return ret;
+}
+
+static int fdevent_solaris_devpoll_event_get_revent(fdevents *ev, size_t ndx) {
+ return ev->devpollfds[ndx].revents;
+}
+
+static int fdevent_solaris_devpoll_event_get_fd(fdevents *ev, size_t ndx) {
+ return ev->devpollfds[ndx].fd;
+}
+
+static int fdevent_solaris_devpoll_event_next_fdndx(fdevents *ev, int last_ndx) {
+ size_t i;
+
+ UNUSED(ev);
+
+ i = (last_ndx < 0) ? 0 : last_ndx + 1;
+
+ return i;
+}
+
+int fdevent_solaris_devpoll_init(fdevents *ev) {
+ ev->type = FDEVENT_HANDLER_SOLARIS_DEVPOLL;
+#define SET(x) \
+ ev->x = fdevent_solaris_devpoll_##x;
+
+ SET(free);
+ SET(poll);
+
+ SET(event_del);
+ SET(event_add);
+
+ SET(event_next_fdndx);
+ SET(event_get_fd);
+ SET(event_get_revent);
+
+ ev->devpollfds = malloc(sizeof(*ev->devpollfds) * ev->maxfds);
+
+ if ((ev->devpoll_fd = open("/dev/poll", O_RDWR)) < 0) {
+ fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ return -1;
+ }
+
+ if (fcntl(ev->devpoll_fd, F_SETFD, FD_CLOEXEC) < 0) {
+ fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__, strerror(errno));
+
+ close(ev->devpoll_fd);
+
+ return -1;
+ }
+
+ return 0;
+}
+
+#else
+int fdevent_solaris_devpoll_init(fdevents *ev) {
+ UNUSED(ev);
+
+ fprintf(stderr, "%s.%d: solaris-devpoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
+ __FILE__, __LINE__);
+
+ return -1;
+}
+#endif
diff --git a/src/file_cache.c b/src/file_cache.c
new file mode 100644
index 00000000..ee16c77a
--- /dev/null
+++ b/src/file_cache.c
@@ -0,0 +1,483 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "log.h"
+#include "file_cache.h"
+#include "fdevent.h"
+#include "etag.h"
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+#include "sys-mmap.h"
+
+/* NetBSD 1.3.x needs it */
+#ifndef MAP_FAILED
+# define MAP_FAILED -1
+#endif
+
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+
+#ifndef HAVE_LSTAT
+#define lstat stat
+#endif
+
+/* don't enable the dir-cache
+ *
+ * F_NOTIFY would be nice but only works with linux-rtsig
+ */
+#undef USE_LINUX_SIGIO
+
+file_cache *file_cache_init(void) {
+ file_cache *fc = NULL;
+
+ fc = calloc(1, sizeof(*fc));
+
+ fc->dir_name = buffer_init();
+
+ return fc;
+}
+
+static file_cache_entry * file_cache_entry_init(void) {
+ file_cache_entry *fce = NULL;
+
+ fce = calloc(1, sizeof(*fce));
+
+ fce->fd = -1;
+ fce->fde_ndx = -1;
+ fce->name = buffer_init();
+ fce->etag = buffer_init();
+ fce->content_type = buffer_init();
+
+ return fce;
+}
+
+static void file_cache_entry_free(server *srv, file_cache_entry *fce) {
+ if (!fce) return;
+
+ if (fce->fd >= 0) {
+ close(fce->fd);
+ srv->cur_fds--;
+ }
+
+ buffer_free(fce->etag);
+ buffer_free(fce->name);
+ buffer_free(fce->content_type);
+
+ if (fce->mmap_p) munmap(fce->mmap_p, fce->mmap_length);
+
+ free(fce);
+}
+
+static int file_cache_entry_reset(server *srv, file_cache_entry *fce) {
+ if (fce->fd < 0) return 0;
+
+ close(fce->fd);
+ srv->cur_fds--;
+
+#ifdef USE_LINUX_SIGIO
+ /* doesn't work anymore */
+ if (fce->fde_ndx != -1) {
+ fdevent_event_del(srv->ev, &(fce->fde_ndx), fce->fd);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ if (fce->mmap_p) {
+ munmap(fce->mmap_p, fce->mmap_length);
+ fce->mmap_p = NULL;
+ }
+ fce->fd = -1;
+
+ buffer_reset(fce->etag);
+ buffer_reset(fce->name);
+ buffer_reset(fce->content_type);
+
+ return 0;
+}
+
+void file_cache_free(server *srv, file_cache *fc) {
+ size_t i;
+ for (i = 0; i < fc->used; i++) {
+ file_cache_entry_free(srv, fc->ptr[i]);
+ }
+
+ free(fc->ptr);
+
+ buffer_free(fc->dir_name);
+
+ free(fc);
+
+}
+
+#ifdef HAVE_XATTR
+int fce_attr_get(buffer *buf, char *name) {
+ int attrlen;
+ int ret;
+
+ attrlen = 1024;
+ buffer_prepare_copy(buf, attrlen);
+ attrlen--;
+ if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) {
+ buf->used = attrlen + 1;
+ buf->ptr[attrlen] = '\0';
+ }
+ return ret;
+}
+#endif
+
+file_cache_entry * file_cache_get_unused_entry(server *srv) {
+ file_cache_entry *fce = NULL;
+ file_cache *fc = srv->file_cache;
+ size_t i;
+
+ if (fc->size == 0) {
+ fc->size = 16;
+ fc->ptr = calloc(fc->size, sizeof(*fc->ptr));
+ fc->used = 0;
+ }
+ for (i = 0; i < fc->used; i++) {
+ file_cache_entry *f = fc->ptr[i];
+
+ if (f->fd == -1) {
+ return f;
+ }
+ }
+
+ if (fc->used < fc->size) {
+ fce = file_cache_entry_init();
+ fc->ptr[fc->used++] = fce;
+ } else {
+ /* the cache is full, time to resize */
+
+ fc->size += 16;
+ fc->ptr = realloc(fc->ptr, sizeof(*fc->ptr) * fc->size);
+
+ fce = file_cache_entry_init();
+ fc->ptr[fc->used++] = fce;
+ }
+
+ return fce;
+}
+
+handler_t file_cache_handle_fdevent(void *_srv, void *_fce, int revent) {
+ size_t i;
+ server *srv = _srv;
+ file_cache_entry *fce = _fce;
+ file_cache *fc = srv->file_cache;;
+
+ UNUSED(revent);
+ /* */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sds", "dir has changed: ", fce->fd, fce->name->ptr);
+#endif
+ /* touch all files below this directory */
+
+ for (i = 0; i < fc->used; i++) {
+ file_cache_entry *f = fc->ptr[i];
+
+ if (fce == f) continue;
+
+ if (0 == strncmp(fce->name->ptr, f->name->ptr, fce->name->used - 1)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ss", "file hit: ", f->name->ptr);
+#endif
+ f->is_dirty = 1;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+#if 0
+/* dead code, might be reused somewhere again */
+
+int file_cache_check_cache() {
+ file_cache_entry *first_unused_fce = NULL;
+ file_cache *fc = srv->file_cache;
+ size_t i;
+
+ /* check the cache */
+ for (i = 0; i < fc->used; i++) {
+ fce = fc->ptr[i];
+
+ if (buffer_is_equal(name, fce->name)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "cache hit:", name);
+
+#ifdef USE_LINUX_SIGIO
+ if (fce->is_dirty == 0) {
+ fce->in_use++;
+ return fce;
+ }
+#endif
+
+ /* get the etag information */
+ if (-1 == (stat(name->ptr, &(fce->st)))) {
+ fce->in_use = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed: ", name, strerror(errno));
+
+ file_cache_entry_reset(srv, fce);
+
+ return NULL;
+ }
+ fce->stat_ts = srv->cur_ts;
+
+ /* create etag */
+ etag_create(srv->file_cache_etag, &(fce->st));
+
+ if (!buffer_is_equal(srv->file_cache_etag, fce->etag)) {
+ size_t s_len = 0, k;
+ /* etag has changed: reopen file */
+
+ file_cache_entry_reset(srv, fce);
+
+ if (-1 == (fce->fd = open(fce->name->ptr, O_RDONLY | O_LARGEFILE))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
+
+ buffer_reset(fce->name);
+ return NULL;
+ }
+
+ srv->cur_fds++;
+
+ buffer_copy_string_buffer(fce->etag, srv->file_cache_etag);
+
+ /* determine mimetype */
+ buffer_reset(fce->content_type);
+
+ s_len = name->used - 1;
+
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ size_t ct_len;
+
+ ct_len = ds->key->used - 1;
+
+ if (buffer_is_equal_right_len(name, ds->key, ct_len)) {
+ buffer_copy_string_buffer(fce->content_type, ds->value);
+ break;
+ }
+ }
+
+#ifdef HAVE_XATTR
+ if (buffer_is_empty(fce->content_type)) {
+ fce_attr_get(fce->content_type, name->ptr);
+ }
+#endif
+ }
+
+#ifdef USE_LINUX_SIGIO
+ fce->is_dirty = 0;
+#endif
+
+ fce->in_use++;
+
+ if (fce->fd == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "fd is still -1 !", fce->name);
+ }
+ if (fce->st.st_size == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "size is still 0 !", fce->name);
+ }
+
+
+
+
+ return fce;
+ }
+
+ if (fce->in_use == 0) {
+ if (!first_unused_fce) first_unused_fce = fce;
+
+ if (srv->cur_ts - fce->stat_ts > 10) {
+ file_cache_entry_reset(srv, fce);
+ }
+ }
+ }
+
+ if (first_unused_fce) {
+ file_cache_entry_reset(srv, fce);
+
+ fce = first_unused_fce;
+ } else {
+ /* not found, insert */
+ fce = file_cache_get_unused_entry(srv);
+ }
+}
+#endif
+
+
+handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce_ptr) {
+ file_cache_entry *fce = NULL;
+ file_cache_entry *o_fce = *o_fce_ptr;
+
+
+ UNUSED(con);
+
+ /* still valid ? */
+ if (o_fce != NULL) {
+ if (buffer_is_equal(name, o_fce->name) &&
+ (o_fce->fd != -1) &&
+ (o_fce->stat_ts == srv->cur_ts)
+ ) {
+ return HANDLER_GO_ON;
+ } else {
+ o_fce->in_use--;
+ }
+ file_cache_entry_reset(srv, o_fce);
+ }
+
+
+ fce = file_cache_get_unused_entry(srv);
+
+ buffer_copy_string_buffer(fce->name, name);
+ fce->in_use = 0;
+ fce->fd = -1;
+
+ if (-1 == (con->conf.follow_symlink ? stat(name->ptr, &(fce->st)) : lstat(name->ptr, &(fce->st)))) {
+ int oerrno = errno;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed:", name, strerror(errno));
+#endif
+ file_cache_entry_reset(srv, fce);
+
+ buffer_reset(fce->name);
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ fce->stat_ts = srv->cur_ts;
+
+ if (S_ISREG(fce->st.st_mode)) {
+ size_t k, s_len;
+#ifdef USE_LINUX_SIGIO
+ file_cache_entry *dir_fce;
+ char *slash;
+#endif
+
+ if (-1 == (fce->fd = open(name->ptr, O_RDONLY | O_LARGEFILE))) {
+ int oerrno = errno;
+ if (errno == EMFILE || errno == EINTR) {
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "open failed for:", name,
+ strerror(errno));
+
+ buffer_reset(fce->name);
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ srv->cur_fds++;
+
+ /* determine mimetype */
+ buffer_reset(fce->content_type);
+
+ s_len = name->used - 1;
+
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ size_t ct_len;
+
+ if (ds->key->used == 0) continue;
+
+ ct_len = ds->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ if (0 == strncmp(name->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ buffer_copy_string_buffer(fce->content_type, ds->value);
+ break;
+ }
+ }
+
+#ifdef HAVE_XATTR
+ if (buffer_is_empty(fce->content_type)) {
+ fce_attr_get(fce->content_type, name->ptr);
+ }
+#endif
+
+ etag_create(fce->etag, &(fce->st));
+
+#ifdef USE_LINUX_SIGIO
+ /* register sigio for the directory */
+ dir_fce = file_cache_get_unused_entry(srv);
+
+ buffer_copy_string_buffer(fc->dir_name, name);
+
+ /* get dirname */
+ if (0 == (slash = strrchr(fc->dir_name->ptr, '/'))) {
+ SEGFAULT();
+ }
+ *(slash+1) = '\0';
+
+ if (-1 == (dir_fce->fd = open(fc->dir_name->ptr, O_RDONLY))) {
+ int oerrno = errno;
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "open failed:", fc->dir_name, strerror(errno));
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ srv->cur_fds++;
+
+ if (fcntl(dir_fce->fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT) < 0) {
+ int oerrno = errno;
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fcntl failed:", strerror(errno));
+
+ close(dir_fce->fd);
+ srv->cur_fds--;
+
+ errno = oerrno;
+ return HANDLER_ERROR;
+ }
+
+ /* ->used is not updated -> no _buffer copy */
+ buffer_copy_string(dir_fce->name, fc->dir_name->ptr);
+
+ /* register fd-handler */
+ fdevent_register(srv->ev, dir_fce->fd, file_cache_handle_fdevent, dir_fce);
+ fdevent_event_add(srv->ev, &(dir_fce->fde_ndx), dir_fce->fd, FDEVENT_IN);
+# if 1
+ log_error_write(srv, __FILE__, __LINE__, "sddb", "fdevent_event_add:", fce->fde_ndx, fce->fd, fce->name);
+# endif
+#endif
+ }
+
+ fce->in_use++;
+
+ *o_fce_ptr = fce;
+
+ return HANDLER_GO_ON;
+}
+
+int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce) {
+ UNUSED(srv);
+ UNUSED(con);
+
+ if (fce->in_use > 0) fce->in_use--;
+ file_cache_entry_reset(srv, fce);
+
+ return 0;
+}
+
diff --git a/src/file_cache.h b/src/file_cache.h
new file mode 100644
index 00000000..b45a7cea
--- /dev/null
+++ b/src/file_cache.h
@@ -0,0 +1,12 @@
+#ifndef _FILE_CACHE_H_
+#define _FILE_CACHE_H_
+
+#include "base.h"
+
+file_cache *file_cache_init(void);
+void file_cache_free(server *srv, file_cache *fc);
+
+handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce);
+int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce);
+
+#endif
diff --git a/src/http_auth.c b/src/http_auth.c
new file mode 100644
index 00000000..2aa0e23f
--- /dev/null
+++ b/src/http_auth.c
@@ -0,0 +1,939 @@
+#include "config.h"
+
+#ifdef HAVE_CRYPT_H
+# include <crypt.h>
+#elif defined(__linux__)
+/* linux needs _XOPEN_SOURCE */
+# define _XOPEN_SOURCE
+#endif
+
+#ifdef HAVE_LIBCRYPT
+# define HAVE_CRYPT
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "server.h"
+#include "log.h"
+#include "http_auth.h"
+#include "http_auth_digest.h"
+#include "stream.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5_global.h"
+# include "md5.h"
+#endif
+
+
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+
+static struct pam_conv conv = {
+ misc_conv,
+ NULL
+};
+#endif
+
+static const char base64_pad = '=';
+
+static const short base64_reverse_table[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+
+static unsigned char * base64_decode(buffer *out, const char *in) {
+ unsigned char *result;
+ int ch, j = 0, k;
+ size_t i;
+
+ size_t in_len = strlen(in);
+
+ buffer_prepare_copy(out, in_len);
+
+ result = (unsigned char *)out->ptr;
+
+ ch = in[0];
+ /* run through the whole string, converting as we go */
+ for (i = 0; i < in_len; i++) {
+ ch = in[i];
+
+ if (ch == '\0') break;
+
+ if (ch == base64_pad) break;
+
+ ch = base64_reverse_table[ch];
+ if (ch < 0) continue;
+
+ switch(i % 4) {
+ case 0:
+ result[j] = ch << 2;
+ break;
+ case 1:
+ result[j++] |= ch >> 4;
+ result[j] = (ch & 0x0f) << 4;
+ break;
+ case 2:
+ result[j++] |= ch >>2;
+ result[j] = (ch & 0x03) << 6;
+ break;
+ case 3:
+ result[j++] |= ch;
+ break;
+ }
+ }
+ k = j;
+ /* mop things up if we ended on a boundary */
+ if (ch == base64_pad) {
+ switch(i % 4) {
+ case 0:
+ case 1:
+ return NULL;
+ case 2:
+ k++;
+ case 3:
+ result[k++] = 0;
+ }
+ }
+ result[k] = '\0';
+
+ out->used = k;
+
+ return result;
+}
+
+static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) {
+ int ret = -1;
+
+ if (!username->used|| !realm->used) return -1;
+
+ if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
+ stream f;
+ char * f_line;
+
+ if (buffer_is_empty(p->conf.auth_htdigest_userfile)) return -1;
+
+ if (0 != stream_open(&f, p->conf.auth_htdigest_userfile)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno));
+
+ return -1;
+ }
+
+ f_line = f.start;
+
+ while (f_line - f.start != f.size) {
+ char *f_user, *f_pwd, *e, *f_realm;
+ size_t u_len, pwd_len, r_len;
+
+ f_user = f_line;
+
+ /*
+ * htdigest format
+ *
+ * user:realm:md5(user:realm:password)
+ */
+
+ if (NULL == (f_realm = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "parsed error in", p->conf.auth_htdigest_userfile,
+ "expected 'username:realm:hashed password'");
+
+ stream_close(&f);
+
+ return -1;
+ }
+
+ if (NULL == (f_pwd = memchr(f_realm + 1, ':', f.size - (f_realm + 1 - f.start)))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "parsed error in", p->conf.auth_plain_userfile,
+ "expected 'username:realm:hashed password'");
+
+ stream_close(&f);
+
+ return -1;
+ }
+
+ /* get pointers to the fields */
+ u_len = f_realm - f_user;
+ f_realm++;
+ r_len = f_pwd - f_realm;
+ f_pwd++;
+
+ if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
+ pwd_len = e - f_pwd;
+ } else {
+ pwd_len = f.size - (f_pwd - f.start);
+ }
+
+ if (username->used - 1 == u_len &&
+ (realm->used - 1 == r_len) &&
+ (0 == strncmp(username->ptr, f_user, u_len)) &&
+ (0 == strncmp(realm->ptr, f_realm, r_len))) {
+ /* found */
+
+ buffer_copy_string_len(password, f_pwd, pwd_len);
+
+ ret = 0;
+ break;
+ }
+
+ /* EOL */
+ if (!e) break;
+
+ f_line = e + 1;
+ }
+
+ stream_close(&f);
+ } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
+ p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
+ stream f;
+ char * f_line;
+ buffer *auth_fn;
+
+ auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile;
+
+ if (buffer_is_empty(auth_fn)) return -1;
+
+ if (0 != stream_open(&f, auth_fn)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening plain-userfile", auth_fn, "failed:", strerror(errno));
+
+ return -1;
+ }
+
+ f_line = f.start;
+
+ while (f_line - f.start != f.size) {
+ char *f_user, *f_pwd, *e;
+ size_t u_len, pwd_len;
+
+ f_user = f_line;
+
+ /*
+ * htpasswd format
+ *
+ * user:crypted passwd
+ */
+
+ if (NULL == (f_pwd = memchr(f_user, ':', f.size - (f_user - f.start) ))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "parsed error in", auth_fn,
+ "expected 'username:hashed password'");
+
+ stream_close(&f);
+
+ return -1;
+ }
+
+ /* get pointers to the fields */
+ u_len = f_pwd - f_user;
+ f_pwd++;
+
+ if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) {
+ pwd_len = e - f_pwd;
+ } else {
+ pwd_len = f.size - (f_pwd - f.start);
+ }
+
+ if (username->used - 1 == u_len &&
+ (0 == strncmp(username->ptr, f_user, u_len))) {
+ /* found */
+
+ buffer_copy_string_len(password, f_pwd, pwd_len);
+
+ ret = 0;
+ break;
+ }
+
+ /* EOL */
+ if (!e) break;
+
+ f_line = e + 1;
+ }
+
+ stream_close(&f);
+ } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
+ ret = 0;
+ } else {
+ return -1;
+ }
+
+ return ret;
+}
+
+static int http_auth_match_rules(server *srv, mod_auth_plugin_data *p, const char *url, const char *username, const char *group, const char *host) {
+ const char *r = NULL, *rules = NULL;
+ size_t i;
+ int username_len;
+ data_string *require;
+ array *req;
+
+ UNUSED(group);
+ UNUSED(host);
+
+ /* check what has to be match to fullfil the request */
+ /* search auth-directives for path */
+ for (i = 0; i < p->conf.auth_require->used; i++) {
+ if (p->conf.auth_require->data[i]->key->used == 0) continue;
+
+ if (0 == strncmp(url, p->conf.auth_require->data[i]->key->ptr, p->conf.auth_require->data[i]->key->used - 1)) {
+ break;
+ }
+ }
+
+ if (i == p->conf.auth_require->used) {
+ return -1;
+ }
+
+ req = ((data_array *)(p->conf.auth_require->data[i]))->value;
+
+ require = (data_string *)array_get_element(req, "require");
+
+ /* user=name1|group=name3|host=name4 */
+
+ /* seperate the string by | */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value);
+#endif
+
+ username_len = username ? strlen(username) : 0;
+
+ r = rules = require->value->ptr;
+
+ while (1) {
+ const char *eq;
+ const char *k, *v, *e;
+ int k_len, v_len, r_len;
+
+ e = strchr(r, '|');
+
+ if (e) {
+ r_len = e - r;
+ } else {
+ r_len = strlen(rules) - (r - rules);
+ }
+
+ /* from r to r + r_len is a rule */
+
+ /* search for = in the rules */
+ if (NULL == (eq = strchr(r, '='))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "= is missing");
+ return -1;
+ }
+
+ /* = out of range */
+ if (eq > r + r_len) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "= out of range");
+
+ return -1;
+ }
+
+ /* the part before the = is user|group|host */
+
+ k = r;
+ k_len = eq - r;
+ v = eq + 1;
+ v_len = r_len - k_len - 1;
+
+ if (k_len == 4) {
+ if (0 == strncmp(k, "user", k_len)) {
+ if (username &&
+ username_len == v_len &&
+ 0 == strncmp(username, v, v_len)) {
+ return 0;
+ }
+ } else if (0 == strncmp(k, "host", k_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
+ return -1;
+ }
+ } else if (k_len == 5) {
+ if (0 == strncmp(k, "group", k_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k);
+ return -1;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
+ return -1;
+ }
+
+ if (!e) break;
+ r = e + 1;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched");
+
+ return -1;
+}
+
+/**
+ *
+ *
+ * @param password password-string from the auth-backend
+ * @param pw password-string from the client
+ */
+
+static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p, array *req, buffer *username, buffer *realm, buffer *password, const char *pw) {
+ UNUSED(srv);
+ UNUSED(req);
+
+ if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
+ /*
+ * htdigest format
+ *
+ * user:realm:md5(user:realm:password)
+ */
+
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ char a1[256];
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)username->ptr, username->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)realm->ptr, realm->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
+ MD5_Final(HA1, &Md5Ctx);
+
+ CvtHex(HA1, a1);
+
+ if (0 == strcmp(password->ptr, a1)) {
+ return 0;
+ }
+ } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
+#ifdef HAVE_CRYPT
+ char salt[3];
+ char *crypted;
+ /*
+ * htpasswd format
+ *
+ * user:crypted password
+ */
+
+ salt[0] = password->ptr[0];
+ salt[1] = password->ptr[1];
+ salt[2] = '\0';
+ crypted = crypt(pw, salt);
+
+ if (0 == strcmp(password->ptr, crypted)) {
+ return 0;
+ }
+#endif
+ } else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
+ if (0 == strcmp(password->ptr, pw)) {
+ return 0;
+ }
+ } else if (p->conf.auth_backend == AUTH_BACKEND_PAM) {
+#ifdef USE_PAM
+ pam_handle_t *pamh=NULL;
+ int retval;
+
+ retval = pam_start("login", username->ptr, &conv, &pamh);
+
+ if (retval == PAM_SUCCESS)
+ retval = pam_authenticate(pamh, 0); /* is user really user? */
+
+ if (retval == PAM_SUCCESS)
+ retval = pam_acct_mgmt(pamh, 0); /* permitted access? */
+
+ /* This is where we have been authorized or not. */
+
+ if (pam_end(pamh,retval) != PAM_SUCCESS) { /* close Linux-PAM */
+ pamh = NULL;
+ log_error_write(srv, __FILE__, __LINE__, "s", "failed to release authenticator");
+ }
+
+ if (retval == PAM_SUCCESS) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Authenticated");
+ return 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Not Authenticated");
+ }
+#endif
+ } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
+#ifdef USE_LDAP
+ LDAP *ldap;
+ LDAPMessage *lm, *first;
+ char *dn;
+ int ret;
+ char *attrs[] = { LDAP_NO_ATTRS, NULL };
+ size_t i;
+
+ /* for now we stay synchronous */
+
+ /*
+ * 1. connect anonymously (done in plugin init)
+ * 2. get DN for uid = username
+ * 3. auth against ldap server
+ * 4. (optional) check a field
+ * 5. disconnect
+ *
+ */
+
+ /* check username
+ *
+ * we have to protect us againt username which modifies out filter in
+ * a unpleasant way
+ */
+
+ for (i = 0; i < username->used - 1; i++) {
+ char c = username->ptr[i];
+
+ if (!isalpha(c) &&
+ !isdigit(c)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbd",
+ "ldap: invalid character (a-zA-Z0-9 allowed) in username:", username, i);
+
+ return -1;
+ }
+ }
+
+
+
+ /* build filter */
+ buffer_copy_string_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
+ buffer_append_string_buffer(p->ldap_filter, username);
+ buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post);
+
+
+ /* 2. */
+ if (LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
+ log_error_write(srv, __FILE__, __LINE__, "sssb",
+ "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter);
+
+ return -1;
+ }
+
+ if (NULL == (first = ldap_first_entry(p->conf.ldap, lm))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
+
+ ldap_msgfree(lm);
+
+ return -1;
+ }
+
+ if (NULL == (dn = ldap_get_dn(p->conf.ldap, first))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
+
+ ldap_msgfree(lm);
+
+ return -1;
+ }
+
+ ldap_msgfree(lm);
+
+
+ /* 3. */
+ if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
+ return -1;
+ }
+
+ ret = LDAP_VERSION3;
+ if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ ldap_unbind_s(ldap);
+
+ return -1;
+ }
+
+
+ if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ ldap_unbind_s(ldap);
+
+ return -1;
+ }
+
+ /* 5. */
+ ldap_unbind_s(ldap);
+
+ /* everything worked, good, access granted */
+
+ return 0;
+#endif
+ }
+ return -1;
+}
+
+int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
+ buffer *username, *password;
+ char *pw;
+
+ data_string *realm;
+
+ realm = (data_string *)array_get_element(req, "realm");
+
+ username = buffer_init();
+ password = buffer_init();
+
+ base64_decode(username, realm_str);
+
+ /* r2 == user:password */
+ if (NULL == (pw = strchr(username->ptr, ':'))) {
+ buffer_free(username);
+
+ log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username);
+
+ return 0;
+ }
+
+ *pw++ = '\0';
+
+ username->used = pw - username->ptr;
+
+ /* copy password to r1 */
+ if (http_auth_get_password(srv, p, username, realm->value, password)) {
+ buffer_free(username);
+ buffer_free(password);
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "get_password failed");
+
+ return 0;
+ }
+
+ /* password doesn't match */
+ if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbb", "password doesn't match", con->uri.path, username);
+
+ buffer_free(username);
+ buffer_free(password);
+
+ return 0;
+ }
+
+ /* value is our allow-rules */
+ if (http_auth_match_rules(srv, p, url->ptr, username->ptr, NULL, NULL)) {
+ buffer_free(username);
+ buffer_free(password);
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match");
+
+ return 0;
+ }
+
+ /* remember the username */
+ buffer_copy_string_buffer(p->auth_user, username);
+
+ buffer_free(username);
+ buffer_free(password);
+
+ return 1;
+}
+
+typedef struct {
+ const char *key;
+ int key_len;
+ char **ptr;
+} digest_kv;
+
+int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) {
+ char a1[256];
+ char a2[256];
+
+ char *username;
+ char *realm;
+ char *nonce;
+ char *uri;
+ char *algorithm;
+ char *qop;
+ char *cnonce;
+ char *nc;
+ char *respons;
+
+ const char *method_get = "GET";
+ const char *method_post = "POST";
+ const char *method_head = "HEAD";
+
+ char *e, *c;
+ const char *m = NULL;
+ int i;
+ buffer *password, *b, *username_buf, *realm_buf;
+
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ HASH HA2;
+ HASH RespHash;
+ HASHHEX HA2Hex;
+
+
+ /* init pointers */
+#define S(x) \
+ x, sizeof(x)-1, NULL
+ digest_kv dkv[10] = {
+ { S("username=") },
+ { S("realm=") },
+ { S("nonce=") },
+ { S("uri=") },
+ { S("algorithm=") },
+ { S("qop=") },
+ { S("cnonce=") },
+ { S("nc=") },
+ { S("response=") },
+
+ { NULL, 0, NULL }
+ };
+#undef S
+
+ dkv[0].ptr = &username;
+ dkv[1].ptr = &realm;
+ dkv[2].ptr = &nonce;
+ dkv[3].ptr = &uri;
+ dkv[4].ptr = &algorithm;
+ dkv[5].ptr = &qop;
+ dkv[6].ptr = &cnonce;
+ dkv[7].ptr = &nc;
+ dkv[8].ptr = &respons;
+ dkv[9].ptr = NULL;
+
+ UNUSED(req);
+
+ for (i = 0; dkv[i].key; i++) {
+ *(dkv[i].ptr) = NULL;
+ }
+
+
+ if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST &&
+ p->conf.auth_backend != AUTH_BACKEND_PLAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: unsupported backend (only htdigest or plain)");
+
+ return -1;
+ }
+
+ b = buffer_init_string(realm_str);
+
+ /* parse credentials from client */
+ for (c = b->ptr; *c; c++) {
+ for (i = 0; dkv[i].key; i++) {
+ if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
+ if ((c[dkv[i].key_len] == '"') &&
+ (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
+ /* value with "..." */
+ *(dkv[i].ptr) = c + dkv[i].key_len + 1;
+ c = e;
+
+ *e = '\0';
+ } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
+ /* value without "...", terminated by ',' */
+ *(dkv[i].ptr) = c + dkv[i].key_len;
+ c = e;
+
+ *e = '\0';
+ } else {
+ /* value without "...", terminated by EOL */
+ *(dkv[i].ptr) = c + dkv[i].key_len;
+ c += strlen(c) - 1;
+ }
+ }
+ }
+ }
+
+ if (p->conf.auth_debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "username", username);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "algorigthm", algorithm);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons);
+ }
+
+ /* check if everything is transmitted */
+ if (!username ||
+ !realm ||
+ !nonce ||
+ !uri ||
+ (qop && !nc && !cnonce) ||
+ !respons ) {
+ /* missing field */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: missing field");
+ return -1;
+ }
+
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET: m = method_get; break;
+ case HTTP_METHOD_POST: m = method_post; break;
+ case HTTP_METHOD_HEAD: m = method_head; break;
+ case HTTP_METHOD_UNSET: break;
+ }
+
+ /* password-string == HA1 */
+ password = buffer_init();
+ username_buf = buffer_init_string(username);
+ realm_buf = buffer_init_string(realm);
+ if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) {
+ buffer_free(password);
+ buffer_free(b);
+ buffer_free(username_buf);
+ buffer_free(realm_buf);
+ return 0;
+ }
+
+ buffer_free(username_buf);
+ buffer_free(realm_buf);
+
+ if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
+ /* generate password from plain-text */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)password->ptr, password->used - 1);
+ MD5_Final(HA1, &Md5Ctx);
+ } else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
+ /* HA1 */
+ /* transform the 32-byte-hex-md5 to a 16-byte-md5 */
+ for (i = 0; i < HASHLEN; i++) {
+ HA1[i] = hex2int(password->ptr[i*2]) << 4;
+ HA1[i] |= hex2int(password->ptr[i*2+1]);
+ }
+ } else {
+ /* we already check that above */
+ SEGFAULT();
+ }
+
+ buffer_free(password);
+
+ if (algorithm &&
+ strcasecmp(algorithm, "md5-sess") == 0) {
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)HA1, 16);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
+ MD5_Final(HA1, &Md5Ctx);
+ }
+
+ CvtHex(HA1, a1);
+
+ /* calculate H(A2) */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
+ if (strcasecmp(qop, "auth-int") == 0) {
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)"", HASHHEXLEN);
+ }
+ MD5_Final(HA2, &Md5Ctx);
+ CvtHex(HA2, HA2Hex);
+
+ /* calculate response */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ if (*qop) {
+ MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ };
+ MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
+ MD5_Final(RespHash, &Md5Ctx);
+ CvtHex(RespHash, a2);
+
+ if (0 != strcmp(a2, respons)) {
+ /* digest not ok */
+
+ if (p->conf.auth_debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssssb",
+ "digest: digest mismatch", a2, respons);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "digest: auth failed for", username, "wrong password");
+
+ buffer_free(b);
+ return 0;
+ }
+
+ /* value is our allow-rules */
+ if (http_auth_match_rules(srv, p, url->ptr, username, NULL, NULL)) {
+ buffer_free(b);
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: rules did match");
+
+ return 0;
+ }
+
+ /* remember the username */
+ buffer_copy_string(p->auth_user, username);
+
+ buffer_free(b);
+
+ if (p->conf.auth_debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "digest: auth ok");
+ }
+ return 1;
+}
+
+
+int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char out[33]) {
+ HASH h;
+ MD5_CTX Md5Ctx;
+ char hh[32];
+
+ UNUSED(p);
+
+ /* generate shared-secret */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)fn->ptr, fn->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
+
+ /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
+ ltostr(hh, srv->cur_ts);
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+ ltostr(hh, rand());
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+
+ MD5_Final(h, &Md5Ctx);
+
+ CvtHex(h, out);
+
+ return 0;
+}
diff --git a/src/http_auth.h b/src/http_auth.h
new file mode 100644
index 00000000..392678b5
--- /dev/null
+++ b/src/http_auth.h
@@ -0,0 +1,64 @@
+#ifndef _HTTP_AUTH_H_
+#define _HTTP_AUTH_H_
+
+#include "server.h"
+#include "plugin.h"
+
+#if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER)
+# define USE_LDAP
+# include <ldap.h>
+#endif
+
+typedef enum { AUTH_BACKEND_UNSET, AUTH_BACKEND_PLAIN,
+ AUTH_BACKEND_LDAP, AUTH_BACKEND_HTPASSWD,
+ AUTH_BACKEND_HTDIGEST, AUTH_BACKEND_PAM } auth_backend_t;
+
+typedef struct {
+ /* auth */
+ array *auth_require;
+
+ buffer *auth_plain_groupfile;
+ buffer *auth_plain_userfile;
+
+ buffer *auth_htdigest_userfile;
+ buffer *auth_htpasswd_userfile;
+
+ buffer *auth_backend_conf;
+
+ buffer *auth_ldap_hostname;
+ buffer *auth_ldap_basedn;
+ buffer *auth_ldap_filter;
+
+ unsigned short auth_debug;
+
+ /* generated */
+ auth_backend_t auth_backend;
+
+#ifdef USE_LDAP
+ LDAP *ldap;
+
+ buffer *ldap_filter_pre;
+ buffer *ldap_filter_post;
+#endif
+} mod_auth_plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *tmp_buf;
+
+ buffer *auth_user;
+
+#ifdef USE_LDAP
+ buffer *ldap_filter;
+#endif
+
+ mod_auth_plugin_config **config_storage;
+
+ mod_auth_plugin_config conf; /* this is only used as long as no handler_ctx is setup */
+} mod_auth_plugin_data;
+
+int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str);
+int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str);
+int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char hh[33]);
+
+#endif
diff --git a/src/http_auth_digest.c b/src/http_auth_digest.c
new file mode 100644
index 00000000..a1dd2d3d
--- /dev/null
+++ b/src/http_auth_digest.c
@@ -0,0 +1,103 @@
+#include <string.h>
+#include "http_auth_digest.h"
+
+#include "config.h"
+#include "buffer.h"
+
+#ifndef USE_OPENSSL
+# include "md5_global.h"
+# include "md5.h"
+#endif
+
+void CvtHex(IN HASH Bin, OUT HASHHEX Hex) {
+ unsigned short i;
+
+ for (i = 0; i < HASHLEN; i++) {
+ Hex[i*2] = int2hex((Bin[i] >> 4) & 0xf);
+ Hex[i*2+1] = int2hex(Bin[i] & 0xf);
+ }
+ Hex[HASHHEXLEN] = '\0';
+}
+
+/* calculate H(A1) as per spec */
+void DigestCalcHA1(
+ IN char * pszAlg,
+ IN char * pszUserName,
+ IN char * pszRealm,
+ IN char * pszPassword,
+ IN char * pszNonce,
+ IN char * pszCNonce,
+ OUT HASHHEX SessionKey
+ )
+{
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszUserName, strlen(pszUserName));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszRealm, strlen(pszRealm));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszPassword, strlen(pszPassword));
+ MD5_Final(HA1, &Md5Ctx);
+ if (strcasecmp(pszAlg, "md5-sess") == 0) {
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)HA1, HASHLEN);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszNonce, strlen(pszNonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszCNonce, strlen(pszCNonce));
+ MD5_Final(HA1, &Md5Ctx);
+ }
+ CvtHex(HA1, SessionKey);
+}
+
+/* calculate request-digest/response-digest as per HTTP Digest spec */
+void DigestCalcResponse(
+ IN HASHHEX HA1, /* H(A1) */
+ IN char * pszNonce, /* nonce from server */
+ IN char * pszNonceCount, /* 8 hex digits */
+ IN char * pszCNonce, /* client nonce */
+ IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
+ IN char * pszMethod, /* method from the request */
+ IN char * pszDigestUri, /* requested URL */
+ IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
+ OUT HASHHEX Response /* request-digest or response-digest */
+ )
+{
+ MD5_CTX Md5Ctx;
+ HASH HA2;
+ HASH RespHash;
+ HASHHEX HA2Hex;
+
+ /* calculate H(A2) */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszMethod, strlen(pszMethod));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszDigestUri, strlen(pszDigestUri));
+ if (strcasecmp(pszQop, "auth-int") == 0) {
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)HEntity, HASHHEXLEN);
+ };
+ MD5_Final(HA2, &Md5Ctx);
+ CvtHex(HA2, HA2Hex);
+
+ /* calculate response */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)HA1, HASHHEXLEN);
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszNonce, strlen(pszNonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ if (*pszQop) {
+ MD5_Update(&Md5Ctx, (unsigned char *)pszNonceCount, strlen(pszNonceCount));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszCNonce, strlen(pszCNonce));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)pszQop, strlen(pszQop));
+ MD5_Update(&Md5Ctx, (unsigned char *)":", 1);
+ }
+ MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
+ MD5_Final(RespHash, &Md5Ctx);
+ CvtHex(RespHash, Response);
+}
+
diff --git a/src/http_auth_digest.h b/src/http_auth_digest.h
new file mode 100644
index 00000000..2d4e363f
--- /dev/null
+++ b/src/http_auth_digest.h
@@ -0,0 +1,46 @@
+#ifndef _DIGCALC_H_
+#define _DIGCALC_H_
+
+#include "config.h"
+
+#define HASHLEN 16
+typedef unsigned char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+#ifdef USE_OPENSSL
+#define IN const
+#else
+#define IN
+#endif
+#define OUT
+
+/* calculate H(A1) as per HTTP Digest spec */
+void DigestCalcHA1(
+ IN char * pszAlg,
+ IN char * pszUserName,
+ IN char * pszRealm,
+ IN char * pszPassword,
+ IN char * pszNonce,
+ IN char * pszCNonce,
+ OUT HASHHEX SessionKey
+ );
+
+/* calculate request-digest/response-digest as per HTTP Digest spec */
+void DigestCalcResponse(
+ IN HASHHEX HA1, /* H(A1) */
+ IN char * pszNonce, /* nonce from server */
+ IN char * pszNonceCount, /* 8 hex digits */
+ IN char * pszCNonce, /* client nonce */
+ IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
+ IN char * pszMethod, /* method from the request */
+ IN char * pszDigestUri, /* requested URL */
+ IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
+ OUT HASHHEX Response /* request-digest or response-digest */
+ );
+
+void CvtHex(
+ IN HASH Bin,
+ OUT HASHHEX Hex
+ );
+
+#endif
diff --git a/src/http_chunk.c b/src/http_chunk.c
new file mode 100644
index 00000000..c128bf1c
--- /dev/null
+++ b/src/http_chunk.c
@@ -0,0 +1,133 @@
+/**
+ * the HTTP chunk-API
+ *
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include "server.h"
+#include "chunk.h"
+#include "http_chunk.h"
+#include "log.h"
+
+static int http_chunk_append_len(server *srv, connection *con, size_t len) {
+ size_t i, olen = len, j;
+ buffer *b;
+
+ b = srv->tmp_chunk_len;
+
+ if (len == 0) {
+ buffer_copy_string(b, "0");
+ } else {
+ for (i = 0; i < 8 && len; i++) {
+ len >>= 4;
+ }
+
+ /* i is the number of hex digits we have */
+ buffer_prepare_copy(b, i + 1);
+
+ for (j = i-1, len = olen; j+1 > 0; j--) {
+ b->ptr[j] = (len & 0xf) + (((len & 0xf) <= 9) ? '0' : 'a' - 10);
+ len >>= 4;
+ }
+ b->used = i;
+ b->ptr[b->used++] = '\0';
+ }
+
+ buffer_append_string(b, "\r\n");
+ chunkqueue_append_buffer(con->write_queue, b);
+
+ return 0;
+}
+
+
+int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, len);
+ }
+
+ chunkqueue_append_file(cq, fn, offset, len);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+int http_chunk_append_buffer(server *srv, connection *con, buffer *mem) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, mem->used - 1);
+ }
+
+ chunkqueue_append_buffer(cq, mem);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && mem->used > 0) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len) {
+ chunkqueue *cq;
+
+ if (!con) return -1;
+
+ cq = con->write_queue;
+
+ if (len == 0) {
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, 0);
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ } else {
+ chunkqueue_append_mem(cq, "", 1);
+ }
+ return 0;
+ }
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ http_chunk_append_len(srv, con, len - 1);
+ }
+
+ chunkqueue_append_mem(cq, mem, len);
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ chunkqueue_append_mem(cq, "\r\n", 2 + 1);
+ }
+
+ return 0;
+}
+
+
+off_t http_chunkqueue_length(server *srv, connection *con) {
+ if (!con) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "connection is NULL!!");
+
+ return 0;
+ }
+
+ return chunkqueue_length(con->write_queue);
+}
diff --git a/src/http_chunk.h b/src/http_chunk.h
new file mode 100644
index 00000000..4ba24a26
--- /dev/null
+++ b/src/http_chunk.h
@@ -0,0 +1,12 @@
+#ifndef _HTTP_CHUNK_H_
+#define _HTTP_CHUNK_H_
+
+#include "server.h"
+#include <sys/types.h>
+
+int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len);
+int http_chunk_append_buffer(server *srv, connection *con, buffer *mem);
+int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len);
+off_t http_chunkqueue_length(server *srv, connection *con);
+
+#endif
diff --git a/src/inet_ntop_cache.c b/src/inet_ntop_cache.c
new file mode 100644
index 00000000..c0b3aa18
--- /dev/null
+++ b/src/inet_ntop_cache.c
@@ -0,0 +1,53 @@
+#include <sys/types.h>
+
+#include <string.h>
+
+
+#include "base.h"
+#include "inet_ntop_cache.h"
+#include "sys-socket.h"
+
+const char * inet_ntop_cache_get_ip(server *srv, sock_addr *addr) {
+#ifdef HAVE_IPV6
+ size_t ndx = 0, i;
+ for (i = 0; i < INET_NTOP_CACHE_MAX; i++) {
+ if (srv->inet_ntop_cache[i].ts != 0) {
+ if (srv->inet_ntop_cache[i].family == AF_INET6 &&
+ 0 == memcmp(srv->inet_ntop_cache[i].addr.ipv6.s6_addr, addr->ipv6.sin6_addr.s6_addr, 16)) {
+ /* IPv6 found in cache */
+ break;
+ } else if (srv->inet_ntop_cache[i].family == AF_INET &&
+ srv->inet_ntop_cache[i].addr.ipv4.s_addr == addr->ipv4.sin_addr.s_addr) {
+ /* IPv4 found in cache */
+ break;
+
+ }
+ }
+ }
+
+ if (i == INET_NTOP_CACHE_MAX) {
+ /* not found in cache */
+
+ i = ndx;
+ inet_ntop(addr->plain.sa_family,
+ addr->plain.sa_family == AF_INET6 ?
+ (const void *) &(addr->ipv6.sin6_addr) :
+ (const void *) &(addr->ipv4.sin_addr),
+ srv->inet_ntop_cache[i].b2, INET6_ADDRSTRLEN);
+
+ srv->inet_ntop_cache[i].ts = srv->cur_ts;
+ srv->inet_ntop_cache[i].family = addr->plain.sa_family;
+
+ if (srv->inet_ntop_cache[i].family == AF_INET) {
+ srv->inet_ntop_cache[i].addr.ipv4.s_addr = addr->ipv4.sin_addr.s_addr;
+ } else if (srv->inet_ntop_cache[i].family == AF_INET6) {
+ memcpy(srv->inet_ntop_cache[i].addr.ipv6.s6_addr, addr->ipv6.sin6_addr.s6_addr, 16);
+ }
+ }
+
+ return srv->inet_ntop_cache[i].b2;
+#else
+ UNUSED(srv);
+ return inet_ntoa(addr->ipv4.sin_addr);
+#endif
+}
diff --git a/src/inet_ntop_cache.h b/src/inet_ntop_cache.h
new file mode 100644
index 00000000..fd3c2814
--- /dev/null
+++ b/src/inet_ntop_cache.h
@@ -0,0 +1,7 @@
+#ifndef _INET_NTOP_CACHE_H_
+#define _INET_NTOP_CACHE_H_
+
+#include "base.h"
+const char * inet_ntop_cache_get_ip(server *srv, sock_addr *addr);
+
+#endif
diff --git a/src/joblist.c b/src/joblist.c
new file mode 100644
index 00000000..dcab9550
--- /dev/null
+++ b/src/joblist.c
@@ -0,0 +1,63 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "joblist.h"
+#include "log.h"
+
+int joblist_append(server *srv, connection *con) {
+ if (con->in_joblist) return 0;
+
+ if (srv->joblist->size == 0) {
+ srv->joblist->size = 16;
+ srv->joblist->ptr = malloc(sizeof(*srv->joblist->ptr) * srv->joblist->size);
+ } else if (srv->joblist->used == srv->joblist->size) {
+ srv->joblist->size += 16;
+ srv->joblist->ptr = realloc(srv->joblist->ptr, sizeof(*srv->joblist->ptr) * srv->joblist->size);
+ }
+
+ srv->joblist->ptr[srv->joblist->used++] = con;
+
+ return 0;
+}
+
+void joblist_free(server *srv, connections *joblist) {
+ UNUSED(srv);
+
+ free(joblist->ptr);
+ free(joblist);
+}
+
+connection *fdwaitqueue_unshift(server *srv, connections *fdwaitqueue) {
+ connection *con;
+ UNUSED(srv);
+
+
+ if (fdwaitqueue->used == 0) return NULL;
+
+ con = fdwaitqueue->ptr[0];
+
+ memmove(fdwaitqueue->ptr, &(fdwaitqueue->ptr[1]), --fdwaitqueue->used * sizeof(*(fdwaitqueue->ptr)));
+
+ return con;
+}
+
+int fdwaitqueue_append(server *srv, connection *con) {
+ if (srv->fdwaitqueue->size == 0) {
+ srv->fdwaitqueue->size = 16;
+ srv->fdwaitqueue->ptr = malloc(sizeof(*(srv->fdwaitqueue->ptr)) * srv->fdwaitqueue->size);
+ } else if (srv->fdwaitqueue->used == srv->fdwaitqueue->size) {
+ srv->fdwaitqueue->size += 16;
+ srv->fdwaitqueue->ptr = realloc(srv->fdwaitqueue->ptr, sizeof(*(srv->fdwaitqueue->ptr)) * srv->fdwaitqueue->size);
+ }
+
+ srv->fdwaitqueue->ptr[srv->fdwaitqueue->used++] = con;
+
+ return 0;
+}
+
+void fdwaitqueue_free(server *srv, connections *fdwaitqueue) {
+ UNUSED(srv);
+ free(fdwaitqueue->ptr);
+ free(fdwaitqueue);
+}
diff --git a/src/joblist.h b/src/joblist.h
new file mode 100644
index 00000000..3701e097
--- /dev/null
+++ b/src/joblist.h
@@ -0,0 +1,13 @@
+#ifndef _JOB_LIST_H_
+#define _JOB_LIST_H_
+
+#include "base.h"
+
+int joblist_append(server *srv, connection *con);
+void joblist_free(server *srv, connections *joblist);
+
+int fdwaitqueue_append(server *srv, connection *con);
+void fdwaitqueue_free(server *srv, connections *fdwaitqueue);
+connection *fdwaitqueue_unshift(server *srv, connections *fdwaitqueue);
+
+#endif
diff --git a/src/keyvalue.c b/src/keyvalue.c
new file mode 100644
index 00000000..1b8eaf8d
--- /dev/null
+++ b/src/keyvalue.c
@@ -0,0 +1,352 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "server.h"
+#include "keyvalue.h"
+
+static keyvalue http_versions[] = {
+ { HTTP_VERSION_1_1, "HTTP/1.1" },
+ { HTTP_VERSION_1_0, "HTTP/1.0" },
+ { HTTP_VERSION_UNSET, NULL }
+};
+
+static keyvalue http_methods[] = {
+ { HTTP_METHOD_GET, "GET" },
+ { HTTP_METHOD_POST, "POST" },
+ { HTTP_METHOD_HEAD, "HEAD" },
+ { HTTP_METHOD_UNSET, NULL }
+};
+
+static keyvalue http_status[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 200, "OK" },
+ { 201, "Created" },
+ { 202, "Accepted" },
+ { 203, "Non-Authoritative Information" },
+ { 204, "No Content" },
+ { 205, "Reset Content" },
+ { 206, "Partial Content" },
+ { 300, "Multiple Choices" },
+ { 301, "Moved Permanently" },
+ { 302, "Found" },
+ { 303, "See Other" },
+ { 304, "Not Modified" },
+ { 305, "Use Proxy" },
+ { 306, "(Unused)" },
+ { 307, "Temporary Redirect" },
+ { 400, "Bad Request" },
+ { 401, "Unauthorized" },
+ { 402, "Payment Required" },
+ { 403, "Forbidden" },
+ { 404, "Not Found" },
+ { 405, "Method Not Allowed" },
+ { 406, "Not Acceptable" },
+ { 407, "Proxy Authentication Required" },
+ { 408, "Request Timeout" },
+ { 409, "Conflict" },
+ { 410, "Gone" },
+ { 411, "Length Required" },
+ { 412, "Precondition Failed" },
+ { 413, "Request Entity Too Large" },
+ { 414, "Request-URI Too Long" },
+ { 415, "Unsupported Media Type" },
+ { 416, "Requested Range Not Satisfiable" },
+ { 417, "Expectation Failed" },
+ { 426, "Upgrade Required" },
+ { 500, "Internal Server Error" },
+ { 501, "Not Implemented" },
+ { 502, "Bad Gateway" },
+ { 503, "Service Not Available" },
+ { 504, "Gateway Timeout" },
+ { 505, "HTTP Version Not Supported" },
+
+ { -1, NULL }
+};
+
+static keyvalue http_status_body[] = {
+ { 400, "400.html" },
+ { 401, "401.html" },
+ { 403, "403.html" },
+ { 404, "404.html" },
+ { 411, "411.html" },
+ { 416, "416.html" },
+ { 500, "500.html" },
+ { 501, "501.html" },
+ { 503, "503.html" },
+ { 505, "505.html" },
+
+ { -1, NULL }
+};
+
+
+const char *keyvalue_get_value(keyvalue *kv, int k) {
+ int i;
+ for (i = 0; kv[i].value; i++) {
+ if (kv[i].key == k) return kv[i].value;
+ }
+ return NULL;
+}
+
+int keyvalue_get_key(keyvalue *kv, const char *s) {
+ int i;
+ for (i = 0; kv[i].value; i++) {
+ if (0 == strcmp(kv[i].value, s)) return kv[i].key;
+ }
+ return -1;
+}
+
+keyvalue_buffer *keyvalue_buffer_init(void) {
+ keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int keyvalue_buffer_append(keyvalue_buffer *kvb, int key, const char *value) {
+ size_t i;
+ if (kvb->size == 0) {
+ kvb->size = 4;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kvb->kv[kvb->used]->key = key;
+ kvb->kv[kvb->used]->value = strdup(value);
+
+ kvb->used++;
+
+ return 0;
+}
+
+void keyvalue_buffer_free(keyvalue_buffer *kvb) {
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->value) free(kvb->kv[i]->value);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+
+ free(kvb);
+}
+
+
+s_keyvalue_buffer *s_keyvalue_buffer_init(void) {
+ s_keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int s_keyvalue_buffer_append(s_keyvalue_buffer *kvb, const char *key, const char *value) {
+ size_t i;
+ if (kvb->size == 0) {
+ kvb->size = 4;
+ kvb->used = 0;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kvb->kv[kvb->used]->key = key ? strdup(key) : NULL;
+ kvb->kv[kvb->used]->value = strdup(value);
+
+ kvb->used++;
+
+ return 0;
+}
+
+void s_keyvalue_buffer_free(s_keyvalue_buffer *kvb) {
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->key) free(kvb->kv[i]->key);
+ if (kvb->kv[i]->value) free(kvb->kv[i]->value);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+
+ free(kvb);
+}
+
+
+httpauth_keyvalue_buffer *httpauth_keyvalue_buffer_init(void) {
+ httpauth_keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int httpauth_keyvalue_buffer_append(httpauth_keyvalue_buffer *kvb, const char *key, const char *realm, httpauth_type type) {
+ size_t i;
+ if (kvb->size == 0) {
+ kvb->size = 4;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ kvb->kv[kvb->used]->key = strdup(key);
+ kvb->kv[kvb->used]->realm = strdup(realm);
+ kvb->kv[kvb->used]->type = type;
+
+ kvb->used++;
+
+ return 0;
+}
+
+void httpauth_keyvalue_buffer_free(httpauth_keyvalue_buffer *kvb) {
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->key) free(kvb->kv[i]->key);
+ if (kvb->kv[i]->realm) free(kvb->kv[i]->realm);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+
+ free(kvb);
+}
+
+
+const char *get_http_version_name(int i) {
+ return keyvalue_get_value(http_versions, i);
+}
+
+const char *get_http_status_name(int i) {
+ return keyvalue_get_value(http_status, i);
+}
+
+const char *get_http_method_name(http_method_t i) {
+ return keyvalue_get_value(http_methods, i);
+}
+
+const char *get_http_status_body_name(int i) {
+ return keyvalue_get_value(http_status_body, i);
+}
+
+int get_http_version_key(const char *s) {
+ return keyvalue_get_key(http_versions, s);
+}
+
+http_method_t get_http_method_key(const char *s) {
+ return (http_method_t)keyvalue_get_key(http_methods, s);
+}
+
+
+
+
+pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void) {
+ pcre_keyvalue_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int pcre_keyvalue_buffer_append(pcre_keyvalue_buffer *kvb, const char *key, const char *value) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+ const char *errptr;
+ int erroff;
+#endif
+
+ if (!key) return -1;
+
+#ifdef HAVE_PCRE_H
+ if (kvb->size == 0) {
+ kvb->size = 4;
+ kvb->used = 0;
+
+ kvb->kv = malloc(kvb->size * sizeof(*kvb->kv));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->kv[i] = calloc(1, sizeof(**kvb->kv));
+ }
+ }
+
+ if (NULL == (kvb->kv[kvb->used]->key = pcre_compile(key,
+ 0, &errptr, &erroff, NULL))) {
+
+ fprintf(stderr, "%s.%d: rexexp compilation error at %s\n", __FILE__, __LINE__, errptr);
+ return -1;
+ }
+
+ kvb->kv[kvb->used]->value = strdup(value);
+
+ kvb->used++;
+
+ return 0;
+#else
+ UNUSED(kvb);
+ UNUSED(value);
+
+ return -1;
+#endif
+}
+
+void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->kv[i]->key) pcre_free(kvb->kv[i]->key);
+ if (kvb->kv[i]->value) free(kvb->kv[i]->value);
+ free(kvb->kv[i]);
+ }
+
+ if (kvb->kv) free(kvb->kv);
+#endif
+
+ free(kvb);
+}
diff --git a/src/keyvalue.h b/src/keyvalue.h
new file mode 100644
index 00000000..3c1f0519
--- /dev/null
+++ b/src/keyvalue.h
@@ -0,0 +1,80 @@
+#ifndef _KEY_VALUE_H_
+#define _KEY_VALUE_H_
+
+#include "config.h"
+
+#ifdef HAVE_PCRE_H
+# include <pcre.h>
+#endif
+
+typedef enum { HTTP_METHOD_UNSET = -1, HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD } http_method_t;
+typedef enum { HTTP_VERSION_UNSET = -1, HTTP_VERSION_1_0, HTTP_VERSION_1_1 } http_version_t;
+
+typedef struct {
+ int key;
+
+ char *value;
+} keyvalue;
+
+typedef struct {
+ char *key;
+
+ char *value;
+} s_keyvalue;
+
+typedef struct {
+#ifdef HAVE_PCRE_H
+ pcre *key;
+#endif
+
+ char *value;
+} pcre_keyvalue;
+
+typedef enum { HTTP_AUTH_BASIC, HTTP_AUTH_DIGEST } httpauth_type;
+
+typedef struct {
+ char *key;
+
+ char *realm;
+ httpauth_type type;
+} httpauth_keyvalue;
+
+#define KVB(x) \
+typedef struct {\
+ x **kv; \
+ size_t used;\
+ size_t size;\
+} x ## _buffer
+
+KVB(keyvalue);
+KVB(s_keyvalue);
+KVB(httpauth_keyvalue);
+KVB(pcre_keyvalue);
+
+const char *get_http_status_name(int i);
+const char *get_http_version_name(int i);
+const char *get_http_method_name(http_method_t i);
+const char *get_http_status_body_name(int i);
+int get_http_version_key(const char *s);
+http_method_t get_http_method_key(const char *s);
+
+const char *keyvalue_get_value(keyvalue *kv, int k);
+int keyvalue_get_key(keyvalue *kv, const char *s);
+
+keyvalue_buffer *keyvalue_buffer_init(void);
+int keyvalue_buffer_append(keyvalue_buffer *kvb, int k, const char *value);
+void keyvalue_buffer_free(keyvalue_buffer *kvb);
+
+s_keyvalue_buffer *s_keyvalue_buffer_init(void);
+int s_keyvalue_buffer_append(s_keyvalue_buffer *kvb, const char *key, const char *value);
+void s_keyvalue_buffer_free(s_keyvalue_buffer *kvb);
+
+httpauth_keyvalue_buffer *httpauth_keyvalue_buffer_init(void);
+int httpauth_keyvalue_buffer_append(httpauth_keyvalue_buffer *kvb, const char *key, const char *realm, httpauth_type type);
+void httpauth_keyvalue_buffer_free(httpauth_keyvalue_buffer *kvb);
+
+pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void);
+int pcre_keyvalue_buffer_append(pcre_keyvalue_buffer *kvb, const char *key, const char *value);
+void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb);
+
+#endif
diff --git a/src/lemon.c b/src/lemon.c
new file mode 100644
index 00000000..55e558db
--- /dev/null
+++ b/src/lemon.c
@@ -0,0 +1,4397 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator. The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+extern void qsort();
+extern double strtod();
+extern long strtol();
+extern void free();
+extern int access();
+extern int atoi();
+
+#ifndef __WIN32__
+# if defined(_WIN32) || defined(WIN32)
+# define __WIN32__
+# endif
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5 /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+char *msort();
+extern void *malloc();
+
+/******** From the file "action.h" *************************************/
+struct action *Action_new();
+struct action *Action_sort();
+void Action_add();
+
+/********* From the file "assert.h" ************************************/
+void myassert();
+#ifndef NDEBUG
+# define assert(X) if(!(X))myassert(__FILE__,__LINE__)
+#else
+# define assert(X)
+#endif
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences();
+void FindFirstSets();
+void FindStates();
+void FindLinks();
+void FindFollowSets();
+void FindActions();
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(/* void */);
+struct config *Configlist_add(/* struct rule *, int */);
+struct config *Configlist_addbasis(/* struct rule *, int */);
+void Configlist_closure(/* void */);
+void Configlist_sort(/* void */);
+void Configlist_sortbasis(/* void */);
+struct config *Configlist_return(/* void */);
+struct config *Configlist_basis(/* void */);
+void Configlist_eat(/* struct config * */);
+void Configlist_reset(/* void */);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+struct s_options {
+ enum { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR,
+ OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type;
+ char *label;
+ char *arg;
+ char *message;
+};
+int OptInit(/* char**,struct s_options*,FILE* */);
+int OptNArgs(/* void */);
+char *OptArg(/* int */);
+void OptErr(/* int */);
+void OptPrint(/* void */);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(/* struct lemon *lemp */);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(/* void */);
+void Plink_add(/* struct plink **, struct config * */);
+void Plink_copy(/* struct plink **, struct plink * */);
+void Plink_delete(/* struct plink * */);
+
+/********** From the file "report.h" *************************************/
+void Reprint(/* struct lemon * */);
+void ReportOutput(/* struct lemon * */);
+void ReportTable(/* struct lemon * */);
+void ReportHeader(/* struct lemon * */);
+void CompressTables(/* struct lemon * */);
+
+/********** From the file "set.h" ****************************************/
+void SetSize(/* int N */); /* All sets will be of size N */
+char *SetNew(/* void */); /* A new set for element 0..N */
+void SetFree(/* char* */); /* Deallocate a set */
+
+int SetAdd(/* char*,int */); /* Add element to a set */
+int SetUnion(/* char *A,char *B */); /* A <- A U B, thru element N */
+
+#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {B_FALSE=0, B_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+struct symbol {
+ char *name; /* Name of the symbol */
+ int index; /* Index number for this symbol */
+ enum {
+ TERMINAL,
+ NONTERMINAL
+ } type; /* Symbols are all either TERMINALS or NTs */
+ struct rule *rule; /* Linked list of rules of this (if an NT) */
+ struct symbol *fallback; /* fallback token in case this token doesn't parse */
+ int prec; /* Precedence if defined (-1 otherwise) */
+ enum e_assoc {
+ LEFT,
+ RIGHT,
+ NONE,
+ UNK
+ } assoc; /* Associativity if predecence is defined */
+ char *firstset; /* First-set for all rules of this symbol */
+ Boolean lambda; /* True if NT and can generate an empty string */
+ char *destructor; /* Code which executes whenever this symbol is
+ ** popped from the stack during error processing */
+ int destructorln; /* Line number of destructor code */
+ char *datatype; /* The data type of information held by this
+ ** object. Only used if type==NONTERMINAL */
+ int dtnum; /* The data type number. In the parser, the value
+ ** stack is a union. The .yy%d element of this
+ ** union is the correct data type for this object */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure. */
+struct rule {
+ struct symbol *lhs; /* Left-hand side of the rule */
+ char *lhsalias; /* Alias for the LHS (NULL if none) */
+ int ruleline; /* Line number for the rule */
+ int nrhs; /* Number of RHS symbols */
+ struct symbol **rhs; /* The RHS symbols */
+ char **rhsalias; /* An alias for each RHS symbol (NULL if none) */
+ int line; /* Line number at which code begins */
+ char *code; /* The code executed when this rule is reduced */
+ struct symbol *precsym; /* Precedence symbol for this rule */
+ int index; /* An index number for this rule */
+ Boolean canReduce; /* True if this rule is ever reduced */
+ struct rule *nextlhs; /* Next rule with the same LHS */
+ struct rule *next; /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+struct config {
+ struct rule *rp; /* The rule upon which the configuration is based */
+ int dot; /* The parse point */
+ char *fws; /* Follow-set for this configuration only */
+ struct plink *fplp; /* Follow-set forward propagation links */
+ struct plink *bplp; /* Follow-set backwards propagation links */
+ struct state *stp; /* Pointer to state which contains this */
+ enum {
+ COMPLETE, /* The status is used during followset and */
+ INCOMPLETE /* shift computations */
+ } status;
+ struct config *next; /* Next configuration in the state */
+ struct config *bp; /* The next basis configuration */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+ struct symbol *sp; /* The look-ahead symbol */
+ enum e_action {
+ SHIFT,
+ ACCEPT,
+ REDUCE,
+ ERROR,
+ CONFLICT, /* Was a reduce, but part of a conflict */
+ SH_RESOLVED, /* Was a shift. Precedence resolved conflict */
+ RD_RESOLVED, /* Was reduce. Precedence resolved conflict */
+ NOT_USED /* Deleted by compression */
+ } type;
+ union {
+ struct state *stp; /* The new state, if a shift */
+ struct rule *rp; /* The rule, if a reduce */
+ } x;
+ struct action *next; /* Next action for this state */
+ struct action *collide; /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+ struct config *bp; /* The basis configurations for this state */
+ struct config *cfp; /* All configurations in this set */
+ int index; /* Sequencial number for this state */
+ struct action *ap; /* Array of actions for this state */
+ int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */
+ int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */
+ int iDflt; /* Default action */
+};
+#define NO_OFFSET (-2147483647)
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+ struct config *cfp; /* The configuration to which linked */
+ struct plink *next; /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows. (LEMON uses no global variables and makes little use of
+** static variables. Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+ struct state **sorted; /* Table of states sorted by state number */
+ struct rule *rule; /* List of all rules */
+ int nstate; /* Number of states */
+ int nrule; /* Number of rules */
+ int nsymbol; /* Number of terminal and nonterminal symbols */
+ int nterminal; /* Number of terminal symbols */
+ struct symbol **symbols; /* Sorted array of pointers to symbols */
+ int errorcnt; /* Number of errors */
+ struct symbol *errsym; /* The error symbol */
+ char *name; /* Name of the generated parser */
+ char *arg; /* Declaration of the 3th argument to parser */
+ char *tokentype; /* Type of terminal symbols in the parser stack */
+ char *vartype; /* The default type of non-terminal symbols */
+ char *start; /* Name of the start symbol for the grammar */
+ char *stacksize; /* Size of the parser stack */
+ char *include; /* Code to put at the start of the C file */
+ int includeln; /* Line number for start of include code */
+ char *error; /* Code to execute when an error is seen */
+ int errorln; /* Line number for start of error code */
+ char *overflow; /* Code to execute on a stack overflow */
+ int overflowln; /* Line number for start of overflow code */
+ char *failure; /* Code to execute on parser failure */
+ int failureln; /* Line number for start of failure code */
+ char *accept; /* Code to execute when the parser excepts */
+ int acceptln; /* Line number for the start of accept code */
+ char *extracode; /* Code appended to the generated file */
+ int extracodeln; /* Line number for the start of the extra code */
+ char *tokendest; /* Code to execute to destroy token data */
+ int tokendestln; /* Line number for token destroyer code */
+ char *vardest; /* Code for the default non-terminal destructor */
+ int vardestln; /* Line number for default non-term destructor code*/
+ char *filename; /* Name of the input file */
+ char *tmplname; /* Name of the template file */
+ char *outname; /* Name of the current output file */
+ char *tokenprefix; /* A prefix added to token names in the .h file */
+ int nconflict; /* Number of parsing conflicts */
+ int tablesize; /* Size of the parse tables */
+ int basisflag; /* Print only basis configurations */
+ int has_fallback; /* True if any %fallback is seen in the grammer */
+ char *argv0; /* Name of the program */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+ extern void memory_error(); \
+ memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+/* Routines for handling a strings */
+
+char *Strsafe();
+
+void Strsafe_init(/* void */);
+int Strsafe_insert(/* char * */);
+char *Strsafe_find(/* char * */);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new();
+int Symbolcmpp(/* struct symbol **, struct symbol ** */);
+void Symbol_init(/* void */);
+int Symbol_insert(/* struct symbol *, char * */);
+struct symbol *Symbol_find(/* char * */);
+struct symbol *Symbol_Nth(/* int */);
+int Symbol_count(/* */);
+struct symbol **Symbol_arrayof(/* */);
+
+/* Routines to manage the state table */
+
+int Configcmp(/* struct config *, struct config * */);
+struct state *State_new();
+void State_init(/* void */);
+int State_insert(/* struct state *, struct config * */);
+struct state *State_find(/* struct config * */);
+struct state **State_arrayof(/* */);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(/* void */);
+int Configtable_insert(/* struct config * */);
+struct config *Configtable_find(/* struct config * */);
+void Configtable_clear(/* int(*)(struct config *) */);
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+struct action *Action_new(){
+ static struct action *freelist = 0;
+ struct action *new;
+
+ if( freelist==0 ){
+ int i;
+ int amt = 100;
+ freelist = (struct action *)malloc( sizeof(struct action)*amt );
+ if( freelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new parser action.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+ freelist[amt-1].next = 0;
+ }
+ new = freelist;
+ freelist = freelist->next;
+ return new;
+}
+
+/* Compare two actions */
+static int actioncmp(ap1,ap2)
+struct action *ap1;
+struct action *ap2;
+{
+ int rc;
+ rc = ap1->sp->index - ap2->sp->index;
+ if( rc==0 ) rc = (int)ap1->type - (int)ap2->type;
+ if( rc==0 ){
+ assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT);
+ assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT);
+ rc = ap1->x.rp->index - ap2->x.rp->index;
+ }
+ return rc;
+}
+
+/* Sort parser actions */
+struct action *Action_sort(ap)
+struct action *ap;
+{
+ ap = (struct action *)msort(ap,&ap->next,actioncmp);
+ return ap;
+}
+
+void Action_add(app,type,sp,arg)
+struct action **app;
+enum e_action type;
+struct symbol *sp;
+char *arg;
+{
+ struct action *new;
+ new = Action_new();
+ new->next = *app;
+ *app = new;
+ new->type = type;
+ new->sp = sp;
+ if( type==SHIFT ){
+ new->x.stp = (struct state *)arg;
+ }else{
+ new->x.rp = (struct rule *)arg;
+ }
+}
+/********************** New code to implement the "acttab" module ***********/
+/*
+** This module implements routines use to construct the yy_action[] table.
+*/
+
+/*
+** The state of the yy_action table under construction is an instance of
+** the following structure
+*/
+typedef struct acttab acttab;
+struct acttab {
+ int nAction; /* Number of used slots in aAction[] */
+ int nActionAlloc; /* Slots allocated for aAction[] */
+ struct {
+ int lookahead; /* Value of the lookahead token */
+ int action; /* Action to take on the given lookahead */
+ } *aAction, /* The yy_action[] table under construction */
+ *aLookahead; /* A single new transaction set */
+ int mnLookahead; /* Minimum aLookahead[].lookahead */
+ int mnAction; /* Action associated with mnLookahead */
+ int mxLookahead; /* Maximum aLookahead[].lookahead */
+ int nLookahead; /* Used slots in aLookahead[] */
+ int nLookaheadAlloc; /* Slots allocated in aLookahead[] */
+};
+
+/* Return the number of entries in the yy_action table */
+#define acttab_size(X) ((X)->nAction)
+
+/* The value for the N-th entry in yy_action */
+#define acttab_yyaction(X,N) ((X)->aAction[N].action)
+
+/* The value for the N-th entry in yy_lookahead */
+#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead)
+
+/* Free all memory associated with the given acttab */
+void acttab_free(acttab *p){
+ free( p->aAction );
+ free( p->aLookahead );
+ free( p );
+}
+
+/* Allocate a new acttab structure */
+acttab *acttab_alloc(void){
+ acttab *p = malloc( sizeof(*p) );
+ if( p==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new acttab.");
+ exit(1);
+ }
+ memset(p, 0, sizeof(*p));
+ return p;
+}
+
+/* Add a new action to the current transaction set
+*/
+void acttab_action(acttab *p, int lookahead, int action){
+ if( p->nLookahead>=p->nLookaheadAlloc ){
+ p->nLookaheadAlloc += 25;
+ p->aLookahead = realloc( p->aLookahead,
+ sizeof(p->aLookahead[0])*p->nLookaheadAlloc );
+ if( p->aLookahead==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ }
+ if( p->nLookahead==0 ){
+ p->mxLookahead = lookahead;
+ p->mnLookahead = lookahead;
+ p->mnAction = action;
+ }else{
+ if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead;
+ if( p->mnLookahead>lookahead ){
+ p->mnLookahead = lookahead;
+ p->mnAction = action;
+ }
+ }
+ p->aLookahead[p->nLookahead].lookahead = lookahead;
+ p->aLookahead[p->nLookahead].action = action;
+ p->nLookahead++;
+}
+
+/*
+** Add the transaction set built up with prior calls to acttab_action()
+** into the current action table. Then reset the transaction set back
+** to an empty set in preparation for a new round of acttab_action() calls.
+**
+** Return the offset into the action table of the new transaction.
+*/
+int acttab_insert(acttab *p){
+ int i, j, k, n;
+ assert( p->nLookahead>0 );
+
+ /* Make sure we have enough space to hold the expanded action table
+ ** in the worst case. The worst case occurs if the transaction set
+ ** must be appended to the current action table
+ */
+ n = p->mxLookahead + 1;
+ if( p->nAction + n >= p->nActionAlloc ){
+ int oldAlloc = p->nActionAlloc;
+ p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
+ p->aAction = realloc( p->aAction,
+ sizeof(p->aAction[0])*p->nActionAlloc);
+ if( p->aAction==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ for(i=oldAlloc; i<p->nActionAlloc; i++){
+ p->aAction[i].lookahead = -1;
+ p->aAction[i].action = -1;
+ }
+ }
+
+ /* Scan the existing action table looking for an offset where we can
+ ** insert the current transaction set. Fall out of the loop when that
+ ** offset is found. In the worst case, we fall out of the loop when
+ ** i reaches p->nAction, which means we append the new transaction set.
+ **
+ ** i is the index in p->aAction[] where p->mnLookahead is inserted.
+ */
+ for(i=0; i<p->nAction+p->mnLookahead; i++){
+ if( p->aAction[i].lookahead<0 ){
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ if( k<0 ) break;
+ if( p->aAction[k].lookahead>=0 ) break;
+ }
+ if( j<p->nLookahead ) continue;
+ for(j=0; j<p->nAction; j++){
+ if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
+ }
+ if( j==p->nAction ){
+ break; /* Fits in empty slots */
+ }
+ }else if( p->aAction[i].lookahead==p->mnLookahead ){
+ if( p->aAction[i].action!=p->mnAction ) continue;
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ if( k<0 || k>=p->nAction ) break;
+ if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break;
+ if( p->aLookahead[j].action!=p->aAction[k].action ) break;
+ }
+ if( j<p->nLookahead ) continue;
+ n = 0;
+ for(j=0; j<p->nAction; j++){
+ if( p->aAction[j].lookahead<0 ) continue;
+ if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++;
+ }
+ if( n==p->nLookahead ){
+ break; /* Same as a prior transaction set */
+ }
+ }
+ }
+ /* Insert transaction set at index i. */
+ for(j=0; j<p->nLookahead; j++){
+ k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+ p->aAction[k] = p->aLookahead[j];
+ if( k>=p->nAction ) p->nAction = k+1;
+ }
+ p->nLookahead = 0;
+
+ /* Return the offset that is added to the lookahead in order to get the
+ ** index into yy_action of the action */
+ return i - p->mnLookahead;
+}
+
+/********************** From the file "assert.c" ****************************/
+/*
+** A more efficient way of handling assertions.
+*/
+void myassert(file,line)
+char *file;
+int line;
+{
+ fprintf(stderr,"Assertion failed on line %d of file \"%s\"\n",line,file);
+ exit(1);
+}
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+**
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled. Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence. If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(xp)
+struct lemon *xp;
+{
+ struct rule *rp;
+ for(rp=xp->rule; rp; rp=rp->next){
+ if( rp->precsym==0 ){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]->prec>=0 ){
+ rp->precsym = rp->rhs[i];
+ break;
+ }
+ }
+ }
+ }
+ return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct rule *rp;
+ int progress;
+
+ for(i=0; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->lambda = B_FALSE;
+ }
+ for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+ lemp->symbols[i]->firstset = SetNew();
+ }
+
+ /* First compute all lambdas */
+ do{
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->lhs->lambda ) continue;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]->lambda==B_FALSE ) break;
+ }
+ if( i==rp->nrhs ){
+ rp->lhs->lambda = B_TRUE;
+ progress = 1;
+ }
+ }
+ }while( progress );
+
+ /* Now compute all first sets */
+ do{
+ struct symbol *s1, *s2;
+ progress = 0;
+ for(rp=lemp->rule; rp; rp=rp->next){
+ s1 = rp->lhs;
+ for(i=0; i<rp->nrhs; i++){
+ s2 = rp->rhs[i];
+ if( s2->type==TERMINAL ){
+ progress += SetAdd(s1->firstset,s2->index);
+ break;
+ }else if( s1==s2 ){
+ if( s1->lambda==B_FALSE ) break;
+ }else{
+ progress += SetUnion(s1->firstset,s2->firstset);
+ if( s2->lambda==B_FALSE ) break;
+ }
+ }
+ }
+ }while( progress );
+ return;
+}
+
+/* Compute all LR(0) states for the grammar. Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(/* struct lemon * */); /* forward reference */
+void FindStates(lemp)
+struct lemon *lemp;
+{
+ struct symbol *sp;
+ struct rule *rp;
+
+ Configlist_init();
+
+ /* Find the start symbol */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ){
+ ErrorMsg(lemp->filename,0,
+"The specified start symbol \"%s\" is not \
+in a nonterminal of the grammar. \"%s\" will be used as the start \
+symbol instead.",lemp->start,lemp->rule->lhs->name);
+ lemp->errorcnt++;
+ sp = lemp->rule->lhs;
+ }
+ }else{
+ sp = lemp->rule->lhs;
+ }
+
+ /* Make sure the start symbol doesn't occur on the right-hand side of
+ ** any rule. Report an error if it does. (YACC would generate a new
+ ** start symbol in this case.) */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ int i;
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhs[i]==sp ){
+ ErrorMsg(lemp->filename,0,
+"The start symbol \"%s\" occurs on the \
+right-hand side of a rule. This will result in a parser which \
+does not work properly.",sp->name);
+ lemp->errorcnt++;
+ }
+ }
+ }
+
+ /* The basis configuration set for the first state
+ ** is all rules which have the start symbol as their
+ ** left-hand side */
+ for(rp=sp->rule; rp; rp=rp->nextlhs){
+ struct config *newcfp;
+ newcfp = Configlist_addbasis(rp,0);
+ SetAdd(newcfp->fws,0);
+ }
+
+ /* Compute the first state. All other states will be
+ ** computed automatically during the computation of the first one.
+ ** The returned pointer to the first state is not used. */
+ (void)getstate(lemp);
+ return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */
+PRIVATE struct state *getstate(lemp)
+struct lemon *lemp;
+{
+ struct config *cfp, *bp;
+ struct state *stp;
+
+ /* Extract the sorted basis of the new state. The basis was constructed
+ ** by prior calls to "Configlist_addbasis()". */
+ Configlist_sortbasis();
+ bp = Configlist_basis();
+
+ /* Get a state with the same basis */
+ stp = State_find(bp);
+ if( stp ){
+ /* A state with the same basis already exists! Copy all the follow-set
+ ** propagation links from the state under construction into the
+ ** preexisting state, then return a pointer to the preexisting state */
+ struct config *x, *y;
+ for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+ Plink_copy(&y->bplp,x->bplp);
+ Plink_delete(x->fplp);
+ x->fplp = x->bplp = 0;
+ }
+ cfp = Configlist_return();
+ Configlist_eat(cfp);
+ }else{
+ /* This really is a new state. Construct all the details */
+ Configlist_closure(lemp); /* Compute the configuration closure */
+ Configlist_sort(); /* Sort the configuration closure */
+ cfp = Configlist_return(); /* Get a pointer to the config list */
+ stp = State_new(); /* A new state structure */
+ MemoryCheck(stp);
+ stp->bp = bp; /* Remember the configuration basis */
+ stp->cfp = cfp; /* Remember the configuration closure */
+ stp->index = lemp->nstate++; /* Every state gets a sequence number */
+ stp->ap = 0; /* No actions, yet. */
+ State_insert(stp,stp->bp); /* Add to the state table */
+ buildshifts(lemp,stp); /* Recursively compute successor states */
+ }
+ return stp;
+}
+
+/* Construct all successor states to the given state. A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(lemp,stp)
+struct lemon *lemp;
+struct state *stp; /* The state from which successors are computed */
+{
+ struct config *cfp; /* For looping thru the config closure of "stp" */
+ struct config *bcfp; /* For the inner loop on config closure of "stp" */
+ struct config *new; /* */
+ struct symbol *sp; /* Symbol following the dot in configuration "cfp" */
+ struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */
+ struct state *newstp; /* A pointer to a successor state */
+
+ /* Each configuration becomes complete after it contibutes to a successor
+ ** state. Initially, all configurations are incomplete */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+ /* Loop through all configurations of the state "stp" */
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */
+ if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */
+ Configlist_reset(); /* Reset the new config set */
+ sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */
+
+ /* For every configuration in the state "stp" which has the symbol "sp"
+ ** following its dot, add the same configuration to the basis set under
+ ** construction but with the dot shifted one symbol to the right. */
+ for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+ if( bcfp->status==COMPLETE ) continue; /* Already used */
+ if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+ bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */
+ if( bsp!=sp ) continue; /* Must be same as for "cfp" */
+ bcfp->status = COMPLETE; /* Mark this config as used */
+ new = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+ Plink_add(&new->bplp,bcfp);
+ }
+
+ /* Get a pointer to the state described by the basis configuration set
+ ** constructed in the preceding loop */
+ newstp = getstate(lemp);
+
+ /* The state "newstp" is reached from the state "stp" by a shift action
+ ** on the symbol "sp" */
+ Action_add(&stp->ap,SHIFT,sp,newstp);
+ }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct config *cfp, *other;
+ struct state *stp;
+ struct plink *plp;
+
+ /* Housekeeping detail:
+ ** Add to every propagate link a pointer back to the state to
+ ** which the link is attached. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ cfp->stp = stp;
+ }
+ }
+
+ /* Convert all backlinks into forward links. Only the forward
+ ** links are used in the follow-set computation. */
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){
+ for(plp=cfp->bplp; plp; plp=plp->next){
+ other = plp->cfp;
+ Plink_add(&other->fplp,cfp);
+ }
+ }
+ }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct config *cfp;
+ struct plink *plp;
+ int progress;
+ int change;
+
+ for(i=0; i<lemp->nstate; i++){
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ cfp->status = INCOMPLETE;
+ }
+ }
+
+ do{
+ progress = 0;
+ for(i=0; i<lemp->nstate; i++){
+ for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+ if( cfp->status==COMPLETE ) continue;
+ for(plp=cfp->fplp; plp; plp=plp->next){
+ change = SetUnion(plp->cfp->fws,cfp->fws);
+ if( change ){
+ plp->cfp->status = INCOMPLETE;
+ progress = 1;
+ }
+ }
+ cfp->status = COMPLETE;
+ }
+ }
+ }while( progress );
+}
+
+static int resolve_conflict();
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(lemp)
+struct lemon *lemp;
+{
+ int i,j;
+ struct config *cfp;
+ struct state *stp;
+ struct symbol *sp;
+ struct rule *rp;
+
+ /* Add all of the reduce actions
+ ** A reduce action is added for each element of the followset of
+ ** a configuration which has its dot at the extreme right.
+ */
+ for(i=0; i<lemp->nstate; i++){ /* Loop over all states */
+ stp = lemp->sorted[i];
+ for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */
+ if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */
+ for(j=0; j<lemp->nterminal; j++){
+ if( SetFind(cfp->fws,j) ){
+ /* Add a reduce action to the state "stp" which will reduce by the
+ ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+ Action_add(&stp->ap,REDUCE,lemp->symbols[j],cfp->rp);
+ }
+ }
+ }
+ }
+ }
+
+ /* Add the accepting token */
+ if( lemp->start ){
+ sp = Symbol_find(lemp->start);
+ if( sp==0 ) sp = lemp->rule->lhs;
+ }else{
+ sp = lemp->rule->lhs;
+ }
+ /* Add to the first state (which is always the starting state of the
+ ** finite state machine) an action to ACCEPT if the lookahead is the
+ ** start nonterminal. */
+ Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+ /* Resolve conflicts */
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap, *nap;
+ struct state *stp;
+ stp = lemp->sorted[i];
+ assert( stp->ap );
+ stp->ap = Action_sort(stp->ap);
+ for(ap=stp->ap; ap && ap->next; ap=ap->next){
+ for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+ /* The two actions "ap" and "nap" have the same lookahead.
+ ** Figure out which one should be used */
+ lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym);
+ }
+ }
+ }
+
+ /* Report an error for each rule that can never be reduced. */
+ for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = B_FALSE;
+ for(i=0; i<lemp->nstate; i++){
+ struct action *ap;
+ for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE ) ap->x.rp->canReduce = B_TRUE;
+ }
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ if( rp->canReduce ) continue;
+ ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+ lemp->errorcnt++;
+ }
+}
+
+/* Resolve a conflict between the two given actions. If the
+** conflict can't be resolve, return non-zero.
+**
+** NO LONGER TRUE:
+** To resolve a conflict, first look to see if either action
+** is on an error rule. In that case, take the action which
+** is not associated with the error rule. If neither or both
+** actions are associated with an error rule, then try to
+** use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx. This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(apx,apy,errsym)
+struct action *apx;
+struct action *apy;
+struct symbol *errsym; /* The error symbol (if defined. NULL otherwise) */
+{
+ struct symbol *spx, *spy;
+ int errcnt = 0;
+ assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */
+ if( apx->type==SHIFT && apy->type==REDUCE ){
+ spx = apx->sp;
+ spy = apy->x.rp->precsym;
+ if( spy==0 || spx->prec<0 || spy->prec<0 ){
+ /* Not enough precedence information. */
+ apy->type = CONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){ /* Lower precedence wins */
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = SH_RESOLVED;
+ }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+ apy->type = RD_RESOLVED; /* associativity */
+ }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */
+ apx->type = SH_RESOLVED;
+ }else{
+ assert( spx->prec==spy->prec && spx->assoc==NONE );
+ apy->type = CONFLICT;
+ errcnt++;
+ }
+ }else if( apx->type==REDUCE && apy->type==REDUCE ){
+ spx = apx->x.rp->precsym;
+ spy = apy->x.rp->precsym;
+ if( spx==0 || spy==0 || spx->prec<0 ||
+ spy->prec<0 || spx->prec==spy->prec ){
+ apy->type = CONFLICT;
+ errcnt++;
+ }else if( spx->prec>spy->prec ){
+ apy->type = RD_RESOLVED;
+ }else if( spx->prec<spy->prec ){
+ apx->type = RD_RESOLVED;
+ }
+ }else{
+ assert(
+ apx->type==SH_RESOLVED ||
+ apx->type==RD_RESOLVED ||
+ apx->type==CONFLICT ||
+ apy->type==SH_RESOLVED ||
+ apy->type==RD_RESOLVED ||
+ apy->type==CONFLICT
+ );
+ /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+ ** REDUCEs on the list. If we reach this point it must be because
+ ** the parser conflict had already been resolved. */
+ }
+ return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0; /* List of free configurations */
+static struct config *current = 0; /* Top of list of configurations */
+static struct config **currentend = 0; /* Last on list of configs */
+static struct config *basis = 0; /* Top of list of basis configs */
+static struct config **basisend = 0; /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(){
+ struct config *new;
+ if( freelist==0 ){
+ int i;
+ int amt = 3;
+ freelist = (struct config *)malloc( sizeof(struct config)*amt );
+ if( freelist==0 ){
+ fprintf(stderr,"Unable to allocate memory for a new configuration.");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+ freelist[amt-1].next = 0;
+ }
+ new = freelist;
+ freelist = freelist->next;
+ return new;
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(old)
+struct config *old;
+{
+ old->next = freelist;
+ freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_init();
+ return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(){
+ current = 0;
+ currentend = &current;
+ basis = 0;
+ basisend = &basis;
+ Configtable_clear(0);
+ return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(rp,dot)
+struct rule *rp; /* The rule */
+int dot; /* Index into the RHS of the rule where the dot goes */
+{
+ struct config *cfp, model;
+
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(rp,dot)
+struct rule *rp;
+int dot;
+{
+ struct config *cfp, model;
+
+ assert( basisend!=0 );
+ assert( currentend!=0 );
+ model.rp = rp;
+ model.dot = dot;
+ cfp = Configtable_find(&model);
+ if( cfp==0 ){
+ cfp = newconfig();
+ cfp->rp = rp;
+ cfp->dot = dot;
+ cfp->fws = SetNew();
+ cfp->stp = 0;
+ cfp->fplp = cfp->bplp = 0;
+ cfp->next = 0;
+ cfp->bp = 0;
+ *currentend = cfp;
+ currentend = &cfp->next;
+ *basisend = cfp;
+ basisend = &cfp->bp;
+ Configtable_insert(cfp);
+ }
+ return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(lemp)
+struct lemon *lemp;
+{
+ struct config *cfp, *newcfp;
+ struct rule *rp, *newrp;
+ struct symbol *sp, *xsp;
+ int i, dot;
+
+ assert( currentend!=0 );
+ for(cfp=current; cfp; cfp=cfp->next){
+ rp = cfp->rp;
+ dot = cfp->dot;
+ if( dot>=rp->nrhs ) continue;
+ sp = rp->rhs[dot];
+ if( sp->type==NONTERMINAL ){
+ if( sp->rule==0 && sp!=lemp->errsym ){
+ ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+ sp->name);
+ lemp->errorcnt++;
+ }
+ for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+ newcfp = Configlist_add(newrp,0);
+ for(i=dot+1; i<rp->nrhs; i++){
+ xsp = rp->rhs[i];
+ if( xsp->type==TERMINAL ){
+ SetAdd(newcfp->fws,xsp->index);
+ break;
+ }else{
+ SetUnion(newcfp->fws,xsp->firstset);
+ if( xsp->lambda==B_FALSE ) break;
+ }
+ }
+ if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+ }
+ }
+ }
+ return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(){
+ current = (struct config *)msort(current,&(current->next),Configcmp);
+ currentend = 0;
+ return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(){
+ basis = (struct config *)msort(current,&(current->bp),Configcmp);
+ basisend = 0;
+ return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(){
+ struct config *old;
+ old = current;
+ current = 0;
+ currentend = 0;
+ return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(){
+ struct config *old;
+ old = basis;
+ basis = 0;
+ basisend = 0;
+ return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(cfp)
+struct config *cfp;
+{
+ struct config *nextcfp;
+ for(; cfp; cfp=nextcfp){
+ nextcfp = cfp->next;
+ assert( cfp->fplp==0 );
+ assert( cfp->bplp==0 );
+ if( cfp->fws ) SetFree(cfp->fws);
+ deleteconfig(cfp);
+ }
+ return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+/* Find a good place to break "msg" so that its length is at least "min"
+** but no more than "max". Make the point as close to max as possible.
+*/
+static int findbreak(msg,min,max)
+char *msg;
+int min;
+int max;
+{
+ int i,spot;
+ char c;
+ for(i=spot=min; i<=max; i++){
+ c = msg[i];
+ if( c=='\t' ) msg[i] = ' ';
+ if( c=='\n' ){ msg[i] = ' '; spot = i; break; }
+ if( c==0 ){ spot = i; break; }
+ if( c=='-' && i<max-1 ) spot = i+1;
+ if( c==' ' ) spot = i;
+ }
+ return spot;
+}
+
+/*
+** The error message is split across multiple lines if necessary. The
+** splits occur at a space, if there is a space available near the end
+** of the line.
+*/
+#define ERRMSGSIZE 10000 /* Hope this is big enough. No way to error check */
+#define LINEWIDTH 79 /* Max width of any output line */
+#define PREFIXLIMIT 30 /* Max width of the prefix on each line */
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+ char errmsg[ERRMSGSIZE];
+ char prefix[PREFIXLIMIT+10];
+ int errmsgsize;
+ int prefixsize;
+ int availablewidth;
+ va_list ap;
+ int end, restart, base;
+
+ va_start(ap, format);
+ /* Prepare a prefix to be prepended to every output line */
+ if( lineno>0 ){
+ sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno);
+ }else{
+ sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename);
+ }
+ prefixsize = strlen(prefix);
+ availablewidth = LINEWIDTH - prefixsize;
+
+ /* Generate the error message */
+ vsprintf(errmsg,format,ap);
+ va_end(ap);
+ errmsgsize = strlen(errmsg);
+ /* Remove trailing '\n's from the error message. */
+ while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){
+ errmsg[--errmsgsize] = 0;
+ }
+
+ /* Print the error message */
+ base = 0;
+ while( errmsg[base]!=0 ){
+ end = restart = findbreak(&errmsg[base],0,availablewidth);
+ restart += base;
+ while( errmsg[restart]==' ' ) restart++;
+ fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]);
+ base = restart;
+ }
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort. This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(){
+ fprintf(stderr,"Out of memory. Aborting...\n");
+ exit(1);
+}
+
+
+/* The main program. Parse the command line and do it... */
+int main(argc,argv)
+int argc;
+char **argv;
+{
+ static int version = 0;
+ static int rpflag = 0;
+ static int basisflag = 0;
+ static int compress = 0;
+ static int quiet = 0;
+ static int statistics = 0;
+ static int mhflag = 0;
+ static struct s_options options[] = {
+ {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+ {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+ {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+ {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file"},
+ {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+ {OPT_FLAG, "s", (char*)&statistics, "Print parser stats to standard output."},
+ {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+ {OPT_FLAG,0,0,0}
+ };
+ int i;
+ struct lemon lem;
+ char *def_tmpl_name = "lempar.c";
+
+ OptInit(argv,options,stderr);
+ if( version ){
+ printf("Lemon version 1.0\n");
+ exit(0);
+ }
+ if( OptNArgs() < 1 ){
+ fprintf(stderr,"Exactly one filename argument is required.\n");
+ exit(1);
+ }
+ lem.errorcnt = 0;
+
+ /* Initialize the machine */
+ Strsafe_init();
+ Symbol_init();
+ State_init();
+ lem.argv0 = argv[0];
+ lem.filename = OptArg(0);
+ lem.tmplname = (OptNArgs() == 2) ? OptArg(1) : def_tmpl_name;
+ lem.basisflag = basisflag;
+ lem.has_fallback = 0;
+ lem.nconflict = 0;
+ lem.name = lem.include = lem.arg = lem.tokentype = lem.start = 0;
+ lem.vartype = 0;
+ lem.stacksize = 0;
+ lem.error = lem.overflow = lem.failure = lem.accept = lem.tokendest =
+ lem.tokenprefix = lem.outname = lem.extracode = 0;
+ lem.vardest = 0;
+ lem.tablesize = 0;
+ Symbol_new("$");
+ lem.errsym = Symbol_new("error");
+
+ /* Parse the input file */
+ Parse(&lem);
+ if( lem.errorcnt ) exit(lem.errorcnt);
+ if( lem.rule==0 ){
+ fprintf(stderr,"Empty grammar.\n");
+ exit(1);
+ }
+
+ /* Count and index the symbols of the grammar */
+ lem.nsymbol = Symbol_count();
+ Symbol_new("{default}");
+ lem.symbols = Symbol_arrayof();
+ for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+ qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*),
+ (int(*)())Symbolcmpp);
+ for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+ for(i=1; isupper(lem.symbols[i]->name[0]); i++);
+ lem.nterminal = i;
+
+ /* Generate a reprint of the grammar, if requested on the command line */
+ if( rpflag ){
+ Reprint(&lem);
+ }else{
+ /* Initialize the size for all follow and first sets */
+ SetSize(lem.nterminal);
+
+ /* Find the precedence for every production rule (that has one) */
+ FindRulePrecedences(&lem);
+
+ /* Compute the lambda-nonterminals and the first-sets for every
+ ** nonterminal */
+ FindFirstSets(&lem);
+
+ /* Compute all LR(0) states. Also record follow-set propagation
+ ** links so that the follow-set can be computed later */
+ lem.nstate = 0;
+ FindStates(&lem);
+ lem.sorted = State_arrayof();
+
+ /* Tie up loose ends on the propagation links */
+ FindLinks(&lem);
+
+ /* Compute the follow set of every reducible configuration */
+ FindFollowSets(&lem);
+
+ /* Compute the action tables */
+ FindActions(&lem);
+
+ /* Compress the action tables */
+ if( compress==0 ) CompressTables(&lem);
+
+ /* Generate a report of the parser generated. (the "y.output" file) */
+ if( !quiet ) ReportOutput(&lem);
+
+ /* Generate the source code for the parser */
+ ReportTable(&lem, mhflag);
+
+ /* Produce a header file for use by the scanner. (This step is
+ ** omitted if the "-m" option is used because makeheaders will
+ ** generate the file for us.) */
+ if( !mhflag ) ReportHeader(&lem);
+ }
+ if( statistics ){
+ printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n",
+ lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule);
+ printf(" %d states, %d parser table entries, %d conflicts\n",
+ lem.nstate, lem.tablesize, lem.nconflict);
+ }
+ if( lem.nconflict ){
+ fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+ }
+ exit(lem.errorcnt + lem.nconflict);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list. Then to sort the list call:
+**
+** ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp. The second argument is a pointer to the pointer to the
+** second element of the linked list. This address is used to compute
+** the offset to the "next" field within the structure. The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((unsigned long)A)+offset))
+
+/*
+** Inputs:
+** a: A sorted, null-terminated linked list. (May be null).
+** b: A sorted, null-terminated linked list. (May be null).
+** cmp: A pointer to the comparison function.
+** offset: Offset in the structure to the "next" field.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** of both a and b.
+**
+** Side effects:
+** The "next" pointers for elements in the lists a and b are
+** changed.
+*/
+static char *merge(a,b,cmp,offset)
+char *a;
+char *b;
+int (*cmp)();
+int offset;
+{
+ char *ptr, *head;
+
+ if( a==0 ){
+ head = b;
+ }else if( b==0 ){
+ head = a;
+ }else{
+ if( (*cmp)(a,b)<0 ){
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ ptr = b;
+ b = NEXT(b);
+ }
+ head = ptr;
+ while( a && b ){
+ if( (*cmp)(a,b)<0 ){
+ NEXT(ptr) = a;
+ ptr = a;
+ a = NEXT(a);
+ }else{
+ NEXT(ptr) = b;
+ ptr = b;
+ b = NEXT(b);
+ }
+ }
+ if( a ) NEXT(ptr) = a;
+ else NEXT(ptr) = b;
+ }
+ return head;
+}
+
+/*
+** Inputs:
+** list: Pointer to a singly-linked list of structures.
+** next: Pointer to pointer to the second element of the list.
+** cmp: A comparison function.
+**
+** Return Value:
+** A pointer to the head of a sorted list containing the elements
+** orginally in list.
+**
+** Side effects:
+** The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+char *msort(list,next,cmp)
+char *list;
+char **next;
+int (*cmp)();
+{
+ unsigned long offset;
+ char *ep;
+ char *set[LISTSIZE];
+ int i;
+ offset = (unsigned long)next - (unsigned long)list;
+ for(i=0; i<LISTSIZE; i++) set[i] = 0;
+ while( list ){
+ ep = list;
+ list = NEXT(list);
+ NEXT(ep) = 0;
+ for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+ ep = merge(ep,set[i],cmp,offset);
+ set[i] = 0;
+ }
+ set[i] = ep;
+ }
+ ep = 0;
+ for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(ep,set[i],cmp,offset);
+ return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(n,k,err)
+int n;
+int k;
+FILE *err;
+{
+ int spcnt, i;
+ spcnt = 0;
+ if( argv[0] ) fprintf(err,"%s",argv[0]);
+ spcnt = strlen(argv[0]) + 1;
+ for(i=1; i<n && argv[i]; i++){
+ fprintf(err," %s",argv[i]);
+ spcnt += strlen(argv[i]+1);
+ }
+ spcnt += k;
+ for(; argv[i]; i++) fprintf(err," %s",argv[i]);
+ if( spcnt<20 ){
+ fprintf(err,"\n%*s^-- here\n",spcnt,"");
+ }else{
+ fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+ }
+}
+
+/*
+** Return the index of the N-th non-switch argument. Return -1
+** if N is out of range.
+*/
+static int argindex(n)
+int n;
+{
+ int i;
+ int dashdash = 0;
+ if( argv!=0 && *argv!=0 ){
+ for(i=1; argv[i]; i++){
+ if( dashdash || !ISOPT(argv[i]) ){
+ if( n==0 ) return i;
+ n--;
+ }
+ if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(i,err)
+int i;
+FILE *err;
+{
+ int v;
+ int errcnt = 0;
+ int j;
+ for(j=0; op[j].label; j++){
+ if( strcmp(&argv[i][1],op[j].label)==0 ) break;
+ }
+ v = argv[i][0]=='-' ? 1 : 0;
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }else if( op[j].type==OPT_FLAG ){
+ *((int*)op[j].arg) = v;
+ }else if( op[j].type==OPT_FFLAG ){
+ (*(void(*)())(op[j].arg))(v);
+ }else{
+ if( err ){
+ fprintf(err,"%smissing argument on switch.\n",emsg);
+ errline(i,1,err);
+ }
+ errcnt++;
+ }
+ return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(i,err)
+int i;
+FILE *err;
+{
+ int lv = 0;
+ double dv = 0.0;
+ char *sv = 0, *end;
+ char *cp;
+ int j;
+ int errcnt = 0;
+ cp = strchr(argv[i],'=');
+ *cp = 0;
+ for(j=0; op[j].label; j++){
+ if( strcmp(argv[i],op[j].label)==0 ) break;
+ }
+ *cp = '=';
+ if( op[j].label==0 ){
+ if( err ){
+ fprintf(err,"%sundefined option.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ }else{
+ cp++;
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ if( err ){
+ fprintf(err,"%soption requires an argument.\n",emsg);
+ errline(i,0,err);
+ }
+ errcnt++;
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ dv = strtod(cp,&end);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in floating-point argument.\n",emsg);
+ errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ lv = strtol(cp,&end,0);
+ if( *end ){
+ if( err ){
+ fprintf(err,"%sillegal character in integer argument.\n",emsg);
+ errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+ }
+ errcnt++;
+ }
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ sv = cp;
+ break;
+ }
+ switch( op[j].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_DBL:
+ *(double*)(op[j].arg) = dv;
+ break;
+ case OPT_FDBL:
+ (*(void(*)())(op[j].arg))(dv);
+ break;
+ case OPT_INT:
+ *(int*)(op[j].arg) = lv;
+ break;
+ case OPT_FINT:
+ (*(void(*)())(op[j].arg))((int)lv);
+ break;
+ case OPT_STR:
+ *(char**)(op[j].arg) = sv;
+ break;
+ case OPT_FSTR:
+ (*(void(*)())(op[j].arg))(sv);
+ break;
+ }
+ }
+ return errcnt;
+}
+
+int OptInit(a,o,err)
+char **a;
+struct s_options *o;
+FILE *err;
+{
+ int errcnt = 0;
+ argv = a;
+ op = o;
+ errstream = err;
+ if( argv && *argv && op ){
+ int i;
+ for(i=1; argv[i]; i++){
+ if( argv[i][0]=='+' || argv[i][0]=='-' ){
+ errcnt += handleflags(i,err);
+ }else if( strchr(argv[i],'=') ){
+ errcnt += handleswitch(i,err);
+ }
+ }
+ }
+ if( errcnt>0 ){
+ fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+ OptPrint();
+ exit(1);
+ }
+ return 0;
+}
+
+int OptNArgs(){
+ int cnt = 0;
+ int dashdash = 0;
+ int i;
+ if( argv!=0 && argv[0]!=0 ){
+ for(i=1; argv[i]; i++){
+ if( dashdash || !ISOPT(argv[i]) ) cnt++;
+ if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+ }
+ }
+ return cnt;
+}
+
+char *OptArg(n)
+int n;
+{
+ int i;
+ i = argindex(n);
+ return i>=0 ? argv[i] : 0;
+}
+
+void OptErr(n)
+int n;
+{
+ int i;
+ i = argindex(n);
+ if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(){
+ int i;
+ int max, len;
+ max = 0;
+ for(i=0; op[i].label; i++){
+ len = strlen(op[i].label) + 1;
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ len += 9; /* length of "<integer>" */
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ len += 6; /* length of "<real>" */
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ len += 8; /* length of "<string>" */
+ break;
+ }
+ if( len>max ) max = len;
+ }
+ for(i=0; op[i].label; i++){
+ switch( op[i].type ){
+ case OPT_FLAG:
+ case OPT_FFLAG:
+ fprintf(errstream," -%-*s %s\n",max,op[i].label,op[i].message);
+ break;
+ case OPT_INT:
+ case OPT_FINT:
+ fprintf(errstream," %s=<integer>%*s %s\n",op[i].label,
+ (int)(max-strlen(op[i].label)-9),"",op[i].message);
+ break;
+ case OPT_DBL:
+ case OPT_FDBL:
+ fprintf(errstream," %s=<real>%*s %s\n",op[i].label,
+ (int)(max-strlen(op[i].label)-6),"",op[i].message);
+ break;
+ case OPT_STR:
+ case OPT_FSTR:
+ fprintf(errstream," %s=<string>%*s %s\n",op[i].label,
+ (int)(max-strlen(op[i].label)-8),"",op[i].message);
+ break;
+ }
+ }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+struct pstate {
+ char *filename; /* Name of the input file */
+ int tokenlineno; /* Linenumber at which current token starts */
+ int errorcnt; /* Number of errors so far */
+ char *tokenstart; /* Text of current token */
+ struct lemon *gp; /* Global state vector */
+ enum e_state {
+ INITIALIZE,
+ WAITING_FOR_DECL_OR_RULE,
+ WAITING_FOR_DECL_KEYWORD,
+ WAITING_FOR_DECL_ARG,
+ WAITING_FOR_PRECEDENCE_SYMBOL,
+ WAITING_FOR_ARROW,
+ IN_RHS,
+ LHS_ALIAS_1,
+ LHS_ALIAS_2,
+ LHS_ALIAS_3,
+ RHS_ALIAS_1,
+ RHS_ALIAS_2,
+ PRECEDENCE_MARK_1,
+ PRECEDENCE_MARK_2,
+ RESYNC_AFTER_RULE_ERROR,
+ RESYNC_AFTER_DECL_ERROR,
+ WAITING_FOR_DESTRUCTOR_SYMBOL,
+ WAITING_FOR_DATATYPE_SYMBOL,
+ WAITING_FOR_FALLBACK_ID
+ } state; /* The state of the parser */
+ struct symbol *fallback; /* The fallback token */
+ struct symbol *lhs; /* Left-hand side of current rule */
+ char *lhsalias; /* Alias for the LHS */
+ int nrhs; /* Number of right-hand side symbols seen */
+ struct symbol *rhs[MAXRHS]; /* RHS symbols */
+ char *alias[MAXRHS]; /* Aliases for each RHS symbol (or NULL) */
+ struct rule *prevrule; /* Previous rule parsed */
+ char *declkeyword; /* Keyword of a declaration */
+ char **declargslot; /* Where the declaration argument should be put */
+ int *decllnslot; /* Where the declaration linenumber is put */
+ enum e_assoc declassoc; /* Assign this association to decl arguments */
+ int preccounter; /* Assign this precedence to decl arguments */
+ struct rule *firstrule; /* Pointer to first rule in the grammar */
+ struct rule *lastrule; /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(psp)
+struct pstate *psp;
+{
+ char *x;
+ x = Strsafe(psp->tokenstart); /* Save the token permanently */
+#if 0
+ printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+ x,psp->state);
+#endif
+ switch( psp->state ){
+ case INITIALIZE:
+ psp->prevrule = 0;
+ psp->preccounter = 0;
+ psp->firstrule = psp->lastrule = 0;
+ psp->gp->nrule = 0;
+ /* Fall thru to next case */
+ case WAITING_FOR_DECL_OR_RULE:
+ if( x[0]=='%' ){
+ psp->state = WAITING_FOR_DECL_KEYWORD;
+ }else if( islower(x[0]) ){
+ psp->lhs = Symbol_new(x);
+ psp->nrhs = 0;
+ psp->lhsalias = 0;
+ psp->state = WAITING_FOR_ARROW;
+ }else if( x[0]=='{' ){
+ if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"There is not prior rule opon which to attach the code \
+fragment which begins on this line.");
+ psp->errorcnt++;
+ }else if( psp->prevrule->code!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"Code fragment beginning on this line is not the first \
+to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->line = psp->tokenlineno;
+ psp->prevrule->code = &x[1];
+ }
+ }else if( x[0]=='[' ){
+ psp->state = PRECEDENCE_MARK_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+ x);
+ psp->errorcnt++;
+ }
+ break;
+ case PRECEDENCE_MARK_1:
+ if( !isupper(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The precedence symbol must be a terminal.");
+ psp->errorcnt++;
+ }else if( psp->prevrule==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "There is no prior rule to assign precedence \"[%s]\".",x);
+ psp->errorcnt++;
+ }else if( psp->prevrule->precsym!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+"Precedence mark on this line is not the first \
+to follow the previous rule.");
+ psp->errorcnt++;
+ }else{
+ psp->prevrule->precsym = Symbol_new(x);
+ }
+ psp->state = PRECEDENCE_MARK_2;
+ break;
+ case PRECEDENCE_MARK_2:
+ if( x[0]!=']' ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"]\" on precedence mark.");
+ psp->errorcnt++;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ break;
+ case WAITING_FOR_ARROW:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else if( x[0]=='(' ){
+ psp->state = LHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Expected to see a \":\" following the LHS symbol \"%s\".",
+ psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_1:
+ if( isalpha(x[0]) ){
+ psp->lhsalias = x;
+ psp->state = LHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+ x,psp->lhs->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = LHS_ALIAS_3;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case LHS_ALIAS_3:
+ if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \"->\" following: \"%s(%s)\".",
+ psp->lhs->name,psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case IN_RHS:
+ if( x[0]=='.' ){
+ struct rule *rp;
+ rp = (struct rule *)malloc( sizeof(struct rule) +
+ sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs );
+ if( rp==0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't allocate enough memory for this rule.");
+ psp->errorcnt++;
+ psp->prevrule = 0;
+ }else{
+ int i;
+ rp->ruleline = psp->tokenlineno;
+ rp->rhs = (struct symbol**)&rp[1];
+ rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]);
+ for(i=0; i<psp->nrhs; i++){
+ rp->rhs[i] = psp->rhs[i];
+ rp->rhsalias[i] = psp->alias[i];
+ }
+ rp->lhs = psp->lhs;
+ rp->lhsalias = psp->lhsalias;
+ rp->nrhs = psp->nrhs;
+ rp->code = 0;
+ rp->precsym = 0;
+ rp->index = psp->gp->nrule++;
+ rp->nextlhs = rp->lhs->rule;
+ rp->lhs->rule = rp;
+ rp->next = 0;
+ if( psp->firstrule==0 ){
+ psp->firstrule = psp->lastrule = rp;
+ }else{
+ psp->lastrule->next = rp;
+ psp->lastrule = rp;
+ }
+ psp->prevrule = rp;
+ }
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( isalpha(x[0]) ){
+ if( psp->nrhs>=MAXRHS ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Too many symbol on RHS or rule beginning at \"%s\".",
+ x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }else{
+ psp->rhs[psp->nrhs] = Symbol_new(x);
+ psp->alias[psp->nrhs] = 0;
+ psp->nrhs++;
+ }
+ }else if( x[0]=='(' && psp->nrhs>0 ){
+ psp->state = RHS_ALIAS_1;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal character on RHS of rule: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_1:
+ if( isalpha(x[0]) ){
+ psp->alias[psp->nrhs-1] = x;
+ psp->state = RHS_ALIAS_2;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+ x,psp->rhs[psp->nrhs-1]->name);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case RHS_ALIAS_2:
+ if( x[0]==')' ){
+ psp->state = IN_RHS;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_RULE_ERROR;
+ }
+ break;
+ case WAITING_FOR_DECL_KEYWORD:
+ if( isalpha(x[0]) ){
+ psp->declkeyword = x;
+ psp->declargslot = 0;
+ psp->decllnslot = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ if( strcmp(x,"name")==0 ){
+ psp->declargslot = &(psp->gp->name);
+ }else if( strcmp(x,"include")==0 ){
+ psp->declargslot = &(psp->gp->include);
+ psp->decllnslot = &psp->gp->includeln;
+ }else if( strcmp(x,"code")==0 ){
+ psp->declargslot = &(psp->gp->extracode);
+ psp->decllnslot = &psp->gp->extracodeln;
+ }else if( strcmp(x,"token_destructor")==0 ){
+ psp->declargslot = &psp->gp->tokendest;
+ psp->decllnslot = &psp->gp->tokendestln;
+ }else if( strcmp(x,"default_destructor")==0 ){
+ psp->declargslot = &psp->gp->vardest;
+ psp->decllnslot = &psp->gp->vardestln;
+ }else if( strcmp(x,"token_prefix")==0 ){
+ psp->declargslot = &psp->gp->tokenprefix;
+ }else if( strcmp(x,"syntax_error")==0 ){
+ psp->declargslot = &(psp->gp->error);
+ psp->decllnslot = &psp->gp->errorln;
+ }else if( strcmp(x,"parse_accept")==0 ){
+ psp->declargslot = &(psp->gp->accept);
+ psp->decllnslot = &psp->gp->acceptln;
+ }else if( strcmp(x,"parse_failure")==0 ){
+ psp->declargslot = &(psp->gp->failure);
+ psp->decllnslot = &psp->gp->failureln;
+ }else if( strcmp(x,"stack_overflow")==0 ){
+ psp->declargslot = &(psp->gp->overflow);
+ psp->decllnslot = &psp->gp->overflowln;
+ }else if( strcmp(x,"extra_argument")==0 ){
+ psp->declargslot = &(psp->gp->arg);
+ }else if( strcmp(x,"token_type")==0 ){
+ psp->declargslot = &(psp->gp->tokentype);
+ }else if( strcmp(x,"default_type")==0 ){
+ psp->declargslot = &(psp->gp->vartype);
+ }else if( strcmp(x,"stack_size")==0 ){
+ psp->declargslot = &(psp->gp->stacksize);
+ }else if( strcmp(x,"start_symbol")==0 ){
+ psp->declargslot = &(psp->gp->start);
+ }else if( strcmp(x,"left")==0 ){
+ psp->preccounter++;
+ psp->declassoc = LEFT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"right")==0 ){
+ psp->preccounter++;
+ psp->declassoc = RIGHT;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"nonassoc")==0 ){
+ psp->preccounter++;
+ psp->declassoc = NONE;
+ psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+ }else if( strcmp(x,"destructor")==0 ){
+ psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+ }else if( strcmp(x,"type")==0 ){
+ psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+ }else if( strcmp(x,"fallback")==0 ){
+ psp->fallback = 0;
+ psp->state = WAITING_FOR_FALLBACK_ID;
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Unknown declaration keyword: \"%%%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal declaration keyword: \"%s\".",x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_DESTRUCTOR_SYMBOL:
+ if( !isalpha(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->destructor;
+ psp->decllnslot = &sp->destructorln;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_DATATYPE_SYMBOL:
+ if( !isalpha(x[0]) ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol name missing after %destructor keyword");
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ psp->declargslot = &sp->datatype;
+ psp->decllnslot = 0;
+ psp->state = WAITING_FOR_DECL_ARG;
+ }
+ break;
+ case WAITING_FOR_PRECEDENCE_SYMBOL:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( isupper(x[0]) ){
+ struct symbol *sp;
+ sp = Symbol_new(x);
+ if( sp->prec>=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Symbol \"%s\" has already be given a precedence.",x);
+ psp->errorcnt++;
+ }else{
+ sp->prec = psp->preccounter;
+ sp->assoc = psp->declassoc;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Can't assign a precedence to \"%s\".",x);
+ psp->errorcnt++;
+ }
+ break;
+ case WAITING_FOR_DECL_ARG:
+ if( (x[0]=='{' || x[0]=='\"' || isalnum(x[0])) ){
+ if( *(psp->declargslot)!=0 ){
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "The argument \"%s\" to declaration \"%%%s\" is not the first.",
+ x[0]=='\"' ? &x[1] : x,psp->declkeyword);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }else{
+ *(psp->declargslot) = (x[0]=='\"' || x[0]=='{') ? &x[1] : x;
+ if( psp->decllnslot ) *psp->decllnslot = psp->tokenlineno;
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }
+ }else{
+ ErrorMsg(psp->filename,psp->tokenlineno,
+ "Illegal argument to %%%s: %s",psp->declkeyword,x);
+ psp->errorcnt++;
+ psp->state = RESYNC_AFTER_DECL_ERROR;
+ }
+ break;
+ case WAITING_FOR_FALLBACK_ID:
+ if( x[0]=='.' ){
+ psp->state = WAITING_FOR_DECL_OR_RULE;
+ }else if( !isupper(x[0]) ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "%%fallback argument \"%s\" should be a token", x);
+ psp->errorcnt++;
+ }else{
+ struct symbol *sp = Symbol_new(x);
+ if( psp->fallback==0 ){
+ psp->fallback = sp;
+ }else if( sp->fallback ){
+ ErrorMsg(psp->filename, psp->tokenlineno,
+ "More than one fallback assigned to token %s", x);
+ psp->errorcnt++;
+ }else{
+ sp->fallback = psp->fallback;
+ psp->gp->has_fallback = 1;
+ }
+ }
+ break;
+ case RESYNC_AFTER_RULE_ERROR:
+/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+** break; */
+ case RESYNC_AFTER_DECL_ERROR:
+ if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+ if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+ break;
+ }
+}
+
+/* In spite of its name, this function is really a scanner. It read
+** in the entire input file (all at once) then tokenizes it. Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(gp)
+struct lemon *gp;
+{
+ struct pstate ps;
+ FILE *fp;
+ char *filebuf;
+ int filesize;
+ int lineno;
+ int c;
+ char *cp, *nextcp;
+ int startline = 0;
+
+ ps.gp = gp;
+ ps.filename = gp->filename;
+ ps.errorcnt = 0;
+ ps.state = INITIALIZE;
+
+ /* Begin by reading the input file */
+ fp = fopen(ps.filename,"rb");
+ if( fp==0 ){
+ ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+ gp->errorcnt++;
+ return;
+ }
+ fseek(fp,0,2);
+ filesize = ftell(fp);
+ rewind(fp);
+ filebuf = (char *)malloc( filesize+1 );
+ if( filebuf==0 ){
+ ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.",
+ filesize+1);
+ gp->errorcnt++;
+ return;
+ }
+ if( fread(filebuf,1,filesize,fp)!=filesize ){
+ ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+ filesize);
+ free(filebuf);
+ gp->errorcnt++;
+ return;
+ }
+ fclose(fp);
+ filebuf[filesize] = 0;
+
+ /* Now scan the text of the input file */
+ lineno = 1;
+ for(cp=filebuf; (c= *cp)!=0; ){
+ if( c=='\n' ) lineno++; /* Keep track of the line number */
+ if( isspace(c) ){ cp++; continue; } /* Skip all white space */
+ if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ continue;
+ }
+ if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */
+ cp+=2;
+ while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c ) cp++;
+ continue;
+ }
+ ps.tokenstart = cp; /* Mark the beginning of the token */
+ ps.tokenlineno = lineno; /* Linenumber on which token begins */
+ if( c=='\"' ){ /* String literals */
+ cp++;
+ while( (c= *cp)!=0 && c!='\"' ){
+ if( c=='\n' ) lineno++;
+ cp++;
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,startline,
+"String starting on this line is not terminated before the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( c=='{' ){ /* A block of C code */
+ int level;
+ cp++;
+ for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+ if( c=='\n' ) lineno++;
+ else if( c=='{' ) level++;
+ else if( c=='}' ) level--;
+ else if( c=='/' && cp[1]=='*' ){ /* Skip comments */
+ int prevc;
+ cp = &cp[2];
+ prevc = 0;
+ while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+ if( c=='\n' ) lineno++;
+ prevc = c;
+ cp++;
+ }
+ }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */
+ cp = &cp[2];
+ while( (c= *cp)!=0 && c!='\n' ) cp++;
+ if( c ) lineno++;
+ }else if( c=='\'' || c=='\"' ){ /* String a character literals */
+ int startchar, prevc;
+ startchar = c;
+ prevc = 0;
+ for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+ if( c=='\n' ) lineno++;
+ if( prevc=='\\' ) prevc = 0;
+ else prevc = c;
+ }
+ }
+ }
+ if( c==0 ){
+ ErrorMsg(ps.filename,ps.tokenlineno,
+"C code starting on this line is not terminated before the end of the file.");
+ ps.errorcnt++;
+ nextcp = cp;
+ }else{
+ nextcp = cp+1;
+ }
+ }else if( isalnum(c) ){ /* Identifiers */
+ while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++;
+ nextcp = cp;
+ }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+ cp += 3;
+ nextcp = cp;
+ }else{ /* All other (one character) operators */
+ cp++;
+ nextcp = cp;
+ }
+ c = *cp;
+ *cp = 0; /* Null terminate the token */
+ parseonetoken(&ps); /* Parse the token */
+ *cp = c; /* Restore the buffer */
+ cp = nextcp;
+ }
+ free(filebuf); /* Release the buffer after parsing */
+ gp->rule = ps.firstrule;
+ gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(){
+ struct plink *new;
+
+ if( plink_freelist==0 ){
+ int i;
+ int amt = 100;
+ plink_freelist = (struct plink *)malloc( sizeof(struct plink)*amt );
+ if( plink_freelist==0 ){
+ fprintf(stderr,
+ "Unable to allocate memory for a new follow-set propagation link.\n");
+ exit(1);
+ }
+ for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+ plink_freelist[amt-1].next = 0;
+ }
+ new = plink_freelist;
+ plink_freelist = plink_freelist->next;
+ return new;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(plpp,cfp)
+struct plink **plpp;
+struct config *cfp;
+{
+ struct plink *new;
+ new = Plink_new();
+ new->next = *plpp;
+ *plpp = new;
+ new->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(to,from)
+struct plink **to;
+struct plink *from;
+{
+ struct plink *nextpl;
+ while( from ){
+ nextpl = from->next;
+ from->next = *to;
+ *to = from;
+ from = nextpl;
+ }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(plp)
+struct plink *plp;
+{
+ struct plink *nextpl;
+
+ while( plp ){
+ nextpl = plp->next;
+ plp->next = plink_freelist;
+ plink_freelist = plp;
+ plp = nextpl;
+ }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix. Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(lemp,suffix)
+struct lemon *lemp;
+char *suffix;
+{
+ char *name;
+ char *cp;
+
+ name = malloc( strlen(lemp->filename) + strlen(suffix) + 5 );
+ if( name==0 ){
+ fprintf(stderr,"Can't allocate space for a filename.\n");
+ exit(1);
+ }
+ /* skip directory, JK */
+ if (NULL == (cp = strrchr(lemp->filename, '/'))) {
+ cp = lemp->filename;
+ } else {
+ cp++;
+ }
+ strcpy(name,cp);
+ cp = strrchr(name,'.');
+ if( cp ) *cp = 0;
+ strcat(name,suffix);
+ return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(lemp,suffix,mode)
+struct lemon *lemp;
+char *suffix;
+char *mode;
+{
+ FILE *fp;
+
+ if( lemp->outname ) free(lemp->outname);
+ lemp->outname = file_makename(lemp, suffix);
+ fp = fopen(lemp->outname,mode);
+ if( fp==0 && *mode=='w' ){
+ fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return fp;
+}
+
+/* Duplicate the input file without comments and without actions
+** on rules */
+void Reprint(lemp)
+struct lemon *lemp;
+{
+ struct rule *rp;
+ struct symbol *sp;
+ int i, j, maxlen, len, ncolumns, skip;
+ printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+ maxlen = 10;
+ for(i=0; i<lemp->nsymbol; i++){
+ sp = lemp->symbols[i];
+ len = strlen(sp->name);
+ if( len>maxlen ) maxlen = len;
+ }
+ ncolumns = 76/(maxlen+5);
+ if( ncolumns<1 ) ncolumns = 1;
+ skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+ for(i=0; i<skip; i++){
+ printf("//");
+ for(j=i; j<lemp->nsymbol; j+=skip){
+ sp = lemp->symbols[j];
+ assert( sp->index==j );
+ printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+ }
+ printf("\n");
+ }
+ for(rp=lemp->rule; rp; rp=rp->next){
+ printf("%s",rp->lhs->name);
+/* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */
+ printf(" ::=");
+ for(i=0; i<rp->nrhs; i++){
+ printf(" %s",rp->rhs[i]->name);
+/* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */
+ }
+ printf(".");
+ if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+/* if( rp->code ) printf("\n %s",rp->code); */
+ printf("\n");
+ }
+}
+
+void ConfigPrint(fp,cfp)
+FILE *fp;
+struct config *cfp;
+{
+ struct rule *rp;
+ int i;
+ rp = cfp->rp;
+ fprintf(fp,"%s ::=",rp->lhs->name);
+ for(i=0; i<=rp->nrhs; i++){
+ if( i==cfp->dot ) fprintf(fp," *");
+ if( i==rp->nrhs ) break;
+ fprintf(fp," %s",rp->rhs[i]->name);
+ }
+}
+
+/* #define TEST */
+#ifdef TEST
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+ int i;
+ char *spacer;
+ spacer = "";
+ fprintf(out,"%12s[","");
+ for(i=0; i<lemp->nterminal; i++){
+ if( SetFind(set,i) ){
+ fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+ spacer = " ";
+ }
+ }
+ fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+ while( plp ){
+ fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->index);
+ ConfigPrint(out,plp->cfp);
+ fprintf(out,"\n");
+ plp = plp->next;
+ }
+}
+#endif
+
+/* Print an action to the given file descriptor. Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(struct action *ap, FILE *fp, int indent){
+ int result = 1;
+ switch( ap->type ){
+ case SHIFT:
+ fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->index);
+ break;
+ case REDUCE:
+ fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index);
+ break;
+ case ACCEPT:
+ fprintf(fp,"%*s accept",indent,ap->sp->name);
+ break;
+ case ERROR:
+ fprintf(fp,"%*s error",indent,ap->sp->name);
+ break;
+ case CONFLICT:
+ fprintf(fp,"%*s reduce %-3d ** Parsing conflict **",
+ indent,ap->sp->name,ap->x.rp->index);
+ break;
+ case SH_RESOLVED:
+ case RD_RESOLVED:
+ case NOT_USED:
+ result = 0;
+ break;
+ }
+ return result;
+}
+
+/* Generate the "y.output" log file */
+void ReportOutput(lemp)
+struct lemon *lemp;
+{
+ int i;
+ struct state *stp;
+ struct config *cfp;
+ struct action *ap;
+ FILE *fp;
+
+ fp = file_open(lemp,".out","w");
+ if( fp==0 ) return;
+ fprintf(fp," \b");
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ fprintf(fp,"State %d:\n",stp->index);
+ if( lemp->basisflag ) cfp=stp->bp;
+ else cfp=stp->cfp;
+ while( cfp ){
+ char buf[20];
+ if( cfp->dot==cfp->rp->nrhs ){
+ sprintf(buf,"(%d)",cfp->rp->index);
+ fprintf(fp," %5s ",buf);
+ }else{
+ fprintf(fp," ");
+ }
+ ConfigPrint(fp,cfp);
+ fprintf(fp,"\n");
+#ifdef TEST
+ SetPrint(fp,cfp->fws,lemp);
+ PlinkPrint(fp,cfp->fplp,"To ");
+ PlinkPrint(fp,cfp->bplp,"From");
+#endif
+ if( lemp->basisflag ) cfp=cfp->bp;
+ else cfp=cfp->next;
+ }
+ fprintf(fp,"\n");
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+ }
+ fprintf(fp,"\n");
+ }
+ fclose(fp);
+ return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the exacutable */
+PRIVATE char *pathsearch(argv0,name,modemask)
+char *argv0;
+char *name;
+int modemask;
+{
+ char *pathlist;
+ char *path,*cp;
+ char c;
+ extern int access();
+
+#ifdef __WIN32__
+ cp = strrchr(argv0,'\\');
+#else
+ cp = strrchr(argv0,'/');
+#endif
+ if( cp ){
+ c = *cp;
+ *cp = 0;
+ path = (char *)malloc( strlen(argv0) + strlen(name) + 2 );
+ if( path ) sprintf(path,"%s/%s",argv0,name);
+ *cp = c;
+ }else{
+ extern char *getenv();
+ pathlist = getenv("PATH");
+ if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+ path = (char *)malloc( strlen(pathlist)+strlen(name)+2 );
+ if( path!=0 ){
+ while( *pathlist ){
+ cp = strchr(pathlist,':');
+ if( cp==0 ) cp = &pathlist[strlen(pathlist)];
+ c = *cp;
+ *cp = 0;
+ sprintf(path,"%s/%s",pathlist,name);
+ *cp = c;
+ if( c==0 ) pathlist = "";
+ else pathlist = &cp[1];
+ if( access(path,modemask)==0 ) break;
+ }
+ }
+ }
+ return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(lemp,ap)
+struct lemon *lemp;
+struct action *ap;
+{
+ int act;
+ switch( ap->type ){
+ case SHIFT: act = ap->x.stp->index; break;
+ case REDUCE: act = ap->x.rp->index + lemp->nstate; break;
+ case ERROR: act = lemp->nstate + lemp->nrule; break;
+ case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break;
+ default: act = -1; break;
+ }
+ return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%". The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(name,in,out,lineno)
+char *name;
+FILE *in;
+FILE *out;
+int *lineno;
+{
+ int i, iStart;
+ char line[LINESIZE];
+ while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+ (*lineno)++;
+ iStart = 0;
+ if( name ){
+ for(i=0; line[i]; i++){
+ if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+ && (i==0 || !isalpha(line[i-1]))
+ ){
+ if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+ fprintf(out,"%s",name);
+ i += 4;
+ iStart = i+1;
+ }
+ }
+ }
+ fprintf(out,"%s",&line[iStart]);
+ }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(lemp)
+struct lemon *lemp;
+{
+
+ char buf[1000];
+ FILE *in;
+ char *tpltname;
+ char *cp;
+
+ cp = strrchr(lemp->filename,'.');
+ if( cp ){
+ sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename);
+ }else{
+ sprintf(buf,"%s.lt",lemp->filename);
+ }
+ if( access(buf,004)==0 ){
+ tpltname = buf;
+ }else if( access(lemp->tmplname,004)==0 ){
+ tpltname = lemp->tmplname;
+ }else{
+ tpltname = pathsearch(lemp->argv0,lemp->tmplname,0);
+ }
+ if( tpltname==0 ){
+ fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+ lemp->tmplname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ in = fopen(tpltname,"r");
+ if( in==0 ){
+ fprintf(stderr,"Can't open the template file \"%s\".\n",lemp->tmplname);
+ lemp->errorcnt++;
+ return 0;
+ }
+ return in;
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(out,lemp,str,strln,lineno)
+FILE *out;
+struct lemon *lemp;
+char *str;
+int strln;
+int *lineno;
+{
+ if( str==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n",strln,lemp->filename); (*lineno)++;
+ while( *str ){
+ if( *str=='\n' ) (*lineno)++;
+ putc(*str,out);
+ str++;
+ }
+ fprintf(out,"\n#line %d \"%s\"\n",*lineno+2,lemp->outname); (*lineno)+=2;
+ return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(out,sp,lemp,lineno)
+FILE *out;
+struct symbol *sp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp = 0;
+
+ int linecnt = 0;
+ if( sp->type==TERMINAL ){
+ cp = lemp->tokendest;
+ if( cp==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n{",lemp->tokendestln,lemp->filename);
+ }else if( sp->destructor ){
+ cp = sp->destructor;
+ fprintf(out,"#line %d \"%s\"\n{",sp->destructorln,lemp->filename);
+ }else if( lemp->vardest ){
+ cp = lemp->vardest;
+ if( cp==0 ) return;
+ fprintf(out,"#line %d \"%s\"\n{",lemp->vardestln,lemp->filename);
+ }
+ for(; *cp; cp++){
+ if( *cp=='$' && cp[1]=='$' ){
+ fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+ cp++;
+ continue;
+ }
+ if( *cp=='\n' ) linecnt++;
+ fputc(*cp,out);
+ }
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname);
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(sp, lemp)
+struct symbol *sp;
+struct lemon *lemp;
+{
+ int ret;
+ if( sp->type==TERMINAL ){
+ ret = lemp->tokendest!=0;
+ }else{
+ ret = lemp->vardest!=0 || sp->destructor!=0;
+ }
+ return ret;
+}
+
+/*
+** Generate code which executes when the rule "rp" is reduced. Write
+** the code to "out". Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(out,rp,lemp,lineno)
+FILE *out;
+struct rule *rp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp, *xp;
+ int linecnt = 0;
+ int i;
+ char lhsused = 0; /* True if the LHS element has been used */
+ char used[MAXRHS]; /* True for each RHS element which is used */
+
+ for(i=0; i<rp->nrhs; i++) used[i] = 0;
+ lhsused = 0;
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+ fprintf(out,"#line %d \"%s\"\n{",rp->line,lemp->filename);
+ for(cp=rp->code; *cp; cp++){
+ if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){
+ char saved;
+ for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++);
+ saved = *xp;
+ *xp = 0;
+ if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+ fprintf(out,"yygotominor.yy%d",rp->lhs->dtnum);
+ cp = xp;
+ lhsused = 1;
+ }else{
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+ fprintf(out,"yymsp[%d].minor.yy%d",i-rp->nrhs+1,rp->rhs[i]->dtnum);
+ cp = xp;
+ used[i] = 1;
+ break;
+ }
+ }
+ }
+ *xp = saved;
+ }
+ if( *cp=='\n' ) linecnt++;
+ fputc(*cp,out);
+ } /* End loop */
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname);
+ } /* End if( rp->code ) */
+
+ /* Check to make sure the LHS has been used */
+ if( rp->lhsalias && !lhsused ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label \"%s\" for \"%s(%s)\" is never used.",
+ rp->lhsalias,rp->lhs->name,rp->lhsalias);
+ lemp->errorcnt++;
+ }
+
+ /* Generate destructor code for RHS symbols which are not used in the
+ ** reduce code */
+ for(i=0; i<rp->nrhs; i++){
+ if( rp->rhsalias[i] && !used[i] ){
+ ErrorMsg(lemp->filename,rp->ruleline,
+ "Label %s for \"%s(%s)\" is never used.",
+ rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+ lemp->errorcnt++;
+ }else if( rp->rhsalias[i]==0 ){
+ if( has_destructor(rp->rhs[i],lemp) ){
+ fprintf(out," yy_destructor(%d,&yymsp[%d].minor);\n",
+ rp->rhs[i]->index,i-rp->nrhs+1); (*lineno)++;
+ }else{
+ fprintf(out," /* No destructor defined for %s */\n",
+ rp->rhs[i]->name);
+ (*lineno)++;
+ }
+ }
+ }
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals. In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(out,lemp,plineno,mhflag)
+FILE *out; /* The output stream */
+struct lemon *lemp; /* The main info structure for this parser */
+int *plineno; /* Pointer to the line number */
+int mhflag; /* True if generating makeheaders output */
+{
+ int lineno = *plineno; /* The line number of the output */
+ char **types; /* A hash table of datatypes */
+ int arraysize; /* Size of the "types" array */
+ int maxdtlength; /* Maximum length of any ".datatype" field. */
+ char *stddt; /* Standardized name for a datatype */
+ int i,j; /* Loop counters */
+ int hash; /* For hashing the name of a type */
+ char *name; /* Name of the parser */
+
+ /* Allocate and initialize types[] and allocate stddt[] */
+ arraysize = lemp->nsymbol * 2;
+ types = (char**)malloc( arraysize * sizeof(char*) );
+ for(i=0; i<arraysize; i++) types[i] = 0;
+ maxdtlength = 0;
+ if( lemp->vartype ){
+ maxdtlength = strlen(lemp->vartype);
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ int len;
+ struct symbol *sp = lemp->symbols[i];
+ if( sp->datatype==0 ) continue;
+ len = strlen(sp->datatype);
+ if( len>maxdtlength ) maxdtlength = len;
+ }
+ stddt = (char*)malloc( maxdtlength*2 + 1 );
+ if( types==0 || stddt==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+
+ /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+ ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is
+ ** used for terminal symbols. If there is no %default_type defined then
+ ** 0 is also used as the .dtnum value for nonterminals which do not specify
+ ** a datatype using the %type directive.
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ char *cp;
+ if( sp==lemp->errsym ){
+ sp->dtnum = arraysize+1;
+ continue;
+ }
+ if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+ sp->dtnum = 0;
+ continue;
+ }
+ cp = sp->datatype;
+ if( cp==0 ) cp = lemp->vartype;
+ j = 0;
+ while( isspace(*cp) ) cp++;
+ while( *cp ) stddt[j++] = *cp++;
+ while( j>0 && isspace(stddt[j-1]) ) j--;
+ stddt[j] = 0;
+ hash = 0;
+ for(j=0; stddt[j]; j++){
+ hash = hash*53 + stddt[j];
+ }
+ hash = (hash & 0x7fffffff)%arraysize;
+ while( types[hash] ){
+ if( strcmp(types[hash],stddt)==0 ){
+ sp->dtnum = hash + 1;
+ break;
+ }
+ hash++;
+ if( hash>=arraysize ) hash = 0;
+ }
+ if( types[hash]==0 ){
+ sp->dtnum = hash + 1;
+ types[hash] = (char*)malloc( strlen(stddt)+1 );
+ if( types[hash]==0 ){
+ fprintf(stderr,"Out of memory.\n");
+ exit(1);
+ }
+ strcpy(types[hash],stddt);
+ }
+ }
+
+ /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+ name = lemp->name ? lemp->name : "Parse";
+ lineno = *plineno;
+ if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+ fprintf(out,"#define %sTOKENTYPE %s\n",name,
+ lemp->tokentype?lemp->tokentype:"void*"); lineno++;
+ if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+ fprintf(out,"typedef union {\n"); lineno++;
+ fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++;
+ for(i=0; i<arraysize; i++){
+ if( types[i]==0 ) continue;
+ fprintf(out," %s yy%d;\n",types[i],i+1); lineno++;
+ free(types[i]);
+ }
+ fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++;
+ free(stddt);
+ free(types);
+ fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+ *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** lwr and upr, inclusive.
+*/
+static const char *minimum_size_type(int lwr, int upr){
+ if( lwr>=0 ){
+ if( upr<=255 ){
+ return "unsigned char";
+ }else if( upr<65535 ){
+ return "unsigned short int";
+ }else{
+ return "unsigned int";
+ }
+ }else if( lwr>=-127 && upr<=127 ){
+ return "signed char";
+ }else if( lwr>=-32767 && upr<32767 ){
+ return "short";
+ }else{
+ return "int";
+ }
+}
+
+/*
+** Each state contains a set of token transaction and a set of
+** nonterminal transactions. Each of these sets makes an instance
+** of the following structure. An array of these structures is used
+** to order the creation of entries in the yy_action[] table.
+*/
+struct axset {
+ struct state *stp; /* A pointer to a state */
+ int isTkn; /* True to use tokens. False for non-terminals */
+ int nAction; /* Number of actions */
+};
+
+/*
+** Compare to axset structures for sorting purposes
+*/
+static int axset_compare(const void *a, const void *b){
+ struct axset *p1 = (struct axset*)a;
+ struct axset *p2 = (struct axset*)b;
+ return p2->nAction - p1->nAction;
+}
+
+/* Generate C source code for the parser */
+void ReportTable(lemp, mhflag)
+struct lemon *lemp;
+int mhflag; /* Output in makeheaders format if true */
+{
+ FILE *out, *in;
+ char line[LINESIZE];
+ int lineno;
+ struct state *stp;
+ struct action *ap;
+ struct rule *rp;
+ struct acttab *pActtab;
+ int i, j, n;
+ char *name;
+ int mnTknOfst, mxTknOfst;
+ int mnNtOfst, mxNtOfst;
+ struct axset *ax;
+
+ in = tplt_open(lemp);
+ if( in==0 ) return;
+ out = file_open(lemp,".c","w");
+ if( out==0 ){
+ fclose(in);
+ return;
+ }
+ lineno = 1;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the include code, if any */
+ tplt_print(out,lemp,lemp->include,lemp->includeln,&lineno);
+ if( mhflag ){
+ char *name = file_makename(lemp, ".h");
+ fprintf(out,"#include \"%s\"\n", name); lineno++;
+ free(name);
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate #defines for all tokens */
+ if( mhflag ){
+ char *prefix;
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ lineno++;
+ }
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the defines */
+ fprintf(out,"/* \001 */\n");
+ fprintf(out,"#define YYCODETYPE %s\n",
+ minimum_size_type(0, lemp->nsymbol+5)); lineno++;
+ fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++;
+ fprintf(out,"#define YYACTIONTYPE %s\n",
+ minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++;
+ print_stack_union(out,lemp,&lineno,mhflag);
+ if( lemp->stacksize ){
+ if( atoi(lemp->stacksize)<=0 ){
+ ErrorMsg(lemp->filename,0,
+"Illegal stack size: [%s]. The stack size should be an integer constant.",
+ lemp->stacksize);
+ lemp->errorcnt++;
+ lemp->stacksize = "100";
+ }
+ fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++;
+ }else{
+ fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#if INTERFACE\n"); lineno++;
+ }
+ name = lemp->name ? lemp->name : "Parse";
+ if( lemp->arg && lemp->arg[0] ){
+ int i;
+ i = strlen(lemp->arg);
+ while( i>=1 && isspace(lemp->arg[i-1]) ) i--;
+ while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+ fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++;
+ fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n",
+ name,lemp->arg,&lemp->arg[i]); lineno++;
+ fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n",
+ name,&lemp->arg[i],&lemp->arg[i]); lineno++;
+ }else{
+ fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+ fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+ fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+ }
+ if( mhflag ){
+ fprintf(out,"#endif\n"); lineno++;
+ }
+ fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++;
+ fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++;
+ fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++;
+ fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++;
+ if( lemp->has_fallback ){
+ fprintf(out,"#define YYFALLBACK 1\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the action table and its associates:
+ **
+ ** yy_action[] A single table containing all actions.
+ ** yy_lookahead[] A table containing the lookahead for each entry in
+ ** yy_action. Used to detect hash collisions.
+ ** yy_shift_ofst[] For each state, the offset into yy_action for
+ ** shifting terminals.
+ ** yy_reduce_ofst[] For each state, the offset into yy_action for
+ ** shifting non-terminals after a reduce.
+ ** yy_default[] Default action for each state.
+ */
+
+ /* Compute the actions on all states and count them up */
+ ax = malloc( sizeof(ax[0])*lemp->nstate*2 );
+ if( ax==0 ){
+ fprintf(stderr,"malloc failed\n");
+ exit(1);
+ }
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ stp->nTknAct = stp->nNtAct = 0;
+ stp->iDflt = lemp->nstate + lemp->nrule;
+ stp->iTknOfst = NO_OFFSET;
+ stp->iNtOfst = NO_OFFSET;
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( compute_action(lemp,ap)>=0 ){
+ if( ap->sp->index<lemp->nterminal ){
+ stp->nTknAct++;
+ }else if( ap->sp->index<lemp->nsymbol ){
+ stp->nNtAct++;
+ }else{
+ stp->iDflt = compute_action(lemp, ap);
+ }
+ }
+ }
+ ax[i*2].stp = stp;
+ ax[i*2].isTkn = 1;
+ ax[i*2].nAction = stp->nTknAct;
+ ax[i*2+1].stp = stp;
+ ax[i*2+1].isTkn = 0;
+ ax[i*2+1].nAction = stp->nNtAct;
+ }
+ mxTknOfst = mnTknOfst = 0;
+ mxNtOfst = mnNtOfst = 0;
+
+ /* Compute the action table. In order to try to keep the size of the
+ ** action table to a minimum, the heuristic of placing the largest action
+ ** sets first is used.
+ */
+ qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare);
+ pActtab = acttab_alloc();
+ for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++){
+ stp = ax[i].stp;
+ if( ax[i].isTkn ){
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action;
+ if( ap->sp->index>=lemp->nterminal ) continue;
+ action = compute_action(lemp, ap);
+ if( action<0 ) continue;
+ acttab_action(pActtab, ap->sp->index, action);
+ }
+ stp->iTknOfst = acttab_insert(pActtab);
+ if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
+ if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
+ }else{
+ for(ap=stp->ap; ap; ap=ap->next){
+ int action;
+ if( ap->sp->index<lemp->nterminal ) continue;
+ if( ap->sp->index==lemp->nsymbol ) continue;
+ action = compute_action(lemp, ap);
+ if( action<0 ) continue;
+ acttab_action(pActtab, ap->sp->index, action);
+ }
+ stp->iNtOfst = acttab_insert(pActtab);
+ if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst;
+ if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
+ }
+ }
+ free(ax);
+
+ /* Output the yy_action table */
+ fprintf(out,"static YYACTIONTYPE yy_action[] = {\n"); lineno++;
+ n = acttab_size(pActtab);
+ for(i=j=0; i<n; i++){
+ int action = acttab_yyaction(pActtab, i);
+ if( action<0 ) action = lemp->nsymbol + lemp->nrule + 2;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", action);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_lookahead table */
+ fprintf(out,"static YYCODETYPE yy_lookahead[] = {\n"); lineno++;
+ for(i=j=0; i<n; i++){
+ int la = acttab_yylookahead(pActtab, i);
+ if( la<0 ) la = lemp->nsymbol;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", la);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_shift_ofst[] table */
+ fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++;
+ fprintf(out, "static %s yy_shift_ofst[] = {\n",
+ minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++;
+ n = lemp->nstate;
+ for(i=j=0; i<n; i++){
+ int ofst;
+ stp = lemp->sorted[i];
+ ofst = stp->iTknOfst;
+ if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", ofst);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the yy_reduce_ofst[] table */
+ fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++;
+ fprintf(out, "static %s yy_reduce_ofst[] = {\n",
+ minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++;
+ n = lemp->nstate;
+ for(i=j=0; i<n; i++){
+ int ofst;
+ stp = lemp->sorted[i];
+ ofst = stp->iNtOfst;
+ if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1;
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", ofst);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+
+ /* Output the default action table */
+ fprintf(out, "static YYACTIONTYPE yy_default[] = {\n"); lineno++;
+ n = lemp->nstate;
+ for(i=j=0; i<n; i++){
+ stp = lemp->sorted[i];
+ if( j==0 ) fprintf(out," /* %5d */ ", i);
+ fprintf(out, " %4d,", stp->iDflt);
+ if( j==9 || i==n-1 ){
+ fprintf(out, "\n"); lineno++;
+ j = 0;
+ }else{
+ j++;
+ }
+ }
+ fprintf(out, "};\n"); lineno++;
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of fallback tokens.
+ */
+ if( lemp->has_fallback ){
+ for(i=0; i<lemp->nterminal; i++){
+ struct symbol *p = lemp->symbols[i];
+ if( p->fallback==0 ){
+ fprintf(out, " 0, /* %10s => nothing */\n", p->name);
+ }else{
+ fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index,
+ p->name, p->fallback->name);
+ }
+ lineno++;
+ }
+ }
+ tplt_xfer(lemp->name, in, out, &lineno);
+
+ /* Generate a table containing the symbolic name of every symbol
+ */
+ for(i=0; i<lemp->nsymbol; i++){
+ sprintf(line,"\"%s\",",lemp->symbols[i]->name);
+ fprintf(out," %-15s",line);
+ if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; }
+ }
+ if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate a table containing a text string that describes every
+ ** rule in the rule set of the grammer. This information is used
+ ** when tracing REDUCE actions.
+ */
+ for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+ assert( rp->index==i );
+ fprintf(out," /* %3d */ \"%s ::=", i, rp->lhs->name);
+ for(j=0; j<rp->nrhs; j++) fprintf(out," %s",rp->rhs[j]->name);
+ fprintf(out,"\",\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes every time a symbol is popped from
+ ** the stack while processing errors or while destroying the parser.
+ ** (In other words, generate the %destructor actions)
+ */
+ if( lemp->tokendest ){
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type!=TERMINAL ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ }
+ for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+ if( i<lemp->nsymbol ){
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ if( lemp->vardest ){
+ struct symbol *dflt_sp = 0;
+ for(i=0; i<lemp->nsymbol; i++){
+ struct symbol *sp = lemp->symbols[i];
+ if( sp==0 || sp->type==TERMINAL ||
+ sp->index<=0 || sp->destructor!=0 ) continue;
+ fprintf(out," case %d:\n",sp->index); lineno++;
+ dflt_sp = sp;
+ }
+ if( dflt_sp!=0 ){
+ emit_destructor_code(out,dflt_sp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes whenever the parser stack overflows */
+ tplt_print(out,lemp,lemp->overflow,lemp->overflowln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate the table of rule information
+ **
+ ** Note: This code depends on the fact that rules are number
+ ** sequentually beginning with 0.
+ */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which execution during each REDUCE action */
+ for(rp=lemp->rule; rp; rp=rp->next){
+ fprintf(out," case %d:\n",rp->index); lineno++;
+ emit_code(out,rp,lemp,&lineno);
+ fprintf(out," break;\n"); lineno++;
+ }
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes if a parse fails */
+ tplt_print(out,lemp,lemp->failure,lemp->failureln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when a syntax error occurs */
+ tplt_print(out,lemp,lemp->error,lemp->errorln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Generate code which executes when the parser accepts its input */
+ tplt_print(out,lemp,lemp->accept,lemp->acceptln,&lineno);
+ tplt_xfer(lemp->name,in,out,&lineno);
+
+ /* Append any addition code the user desires */
+ tplt_print(out,lemp,lemp->extracode,lemp->extracodeln,&lineno);
+
+ fclose(in);
+ fclose(out);
+ return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(lemp)
+struct lemon *lemp;
+{
+ FILE *out, *in;
+ char *prefix;
+ char line[LINESIZE];
+ char pattern[LINESIZE];
+ int i;
+
+ if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+ else prefix = "";
+ in = file_open(lemp,".h","r");
+ if( in ){
+ for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+ sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ if( strcmp(line,pattern) ) break;
+ }
+ fclose(in);
+ if( i==lemp->nterminal ){
+ /* No change in the file. Don't rewrite it. */
+ return;
+ }
+ }
+ out = file_open(lemp,".h","w");
+ if( out ){
+ for(i=1; i<lemp->nterminal; i++){
+ fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+ }
+ fclose(out);
+ }
+ return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default. Only default a reduce if there are more than one.
+*/
+void CompressTables(lemp)
+struct lemon *lemp;
+{
+ struct state *stp;
+ struct action *ap, *ap2;
+ struct rule *rp, *rp2, *rbest;
+ int nbest, n;
+ int i;
+
+ for(i=0; i<lemp->nstate; i++){
+ stp = lemp->sorted[i];
+ nbest = 0;
+ rbest = 0;
+
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type!=REDUCE ) continue;
+ rp = ap->x.rp;
+ if( rp==rbest ) continue;
+ n = 1;
+ for(ap2=ap->next; ap2; ap2=ap2->next){
+ if( ap2->type!=REDUCE ) continue;
+ rp2 = ap2->x.rp;
+ if( rp2==rbest ) continue;
+ if( rp2==rp ) n++;
+ }
+ if( n>nbest ){
+ nbest = n;
+ rbest = rp;
+ }
+ }
+
+ /* Do not make a default if the number of rules to default
+ ** is not at least 2 */
+ if( nbest<2 ) continue;
+
+
+ /* Combine matching REDUCE actions into a single default */
+ for(ap=stp->ap; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+ }
+ assert( ap );
+ ap->sp = Symbol_new("{default}");
+ for(ap=ap->next; ap; ap=ap->next){
+ if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+ }
+ stp->ap = Action_sort(stp->ap);
+ }
+}
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int size = 0;
+
+/* Set the set size */
+void SetSize(n)
+int n;
+{
+ size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(){
+ char *s;
+ int i;
+ s = (char*)malloc( size );
+ if( s==0 ){
+ extern void memory_error();
+ memory_error();
+ }
+ for(i=0; i<size; i++) s[i] = 0;
+ return s;
+}
+
+/* Deallocate a set */
+void SetFree(s)
+char *s;
+{
+ free(s);
+}
+
+/* Add a new element to the set. Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(s,e)
+char *s;
+int e;
+{
+ int rv;
+ rv = s[e];
+ s[e] = 1;
+ return !rv;
+}
+
+/* Add every element of s2 to s1. Return TRUE if s1 changes. */
+int SetUnion(s1,s2)
+char *s1;
+char *s2;
+{
+ int i, progress;
+ progress = 0;
+ for(i=0; i<size; i++){
+ if( s2[i]==0 ) continue;
+ if( s1[i]==0 ){
+ progress = 1;
+ s1[i] = 1;
+ }
+ }
+ return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+** "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file! Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE int strhash(x)
+char *x;
+{
+ int h = 0;
+ while( *x) h = h*13 + *(x++);
+ return h;
+}
+
+/* Works like strdup, sort of. Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+char *Strsafe(y)
+char *y;
+{
+ char *z;
+
+ z = Strsafe_find(y);
+ if( z==0 && (z=malloc( strlen(y)+1 ))!=0 ){
+ strcpy(z,y);
+ Strsafe_insert(z);
+ }
+ MemoryCheck(z);
+ return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x1node *tbl; /* The data stored here */
+ struct s_x1node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+ char *data; /* The data */
+ struct s_x1node *next; /* Next entry with the same hash */
+ struct s_x1node **from; /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(){
+ if( x1a ) return;
+ x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+ if( x1a ){
+ x1a->size = 1024;
+ x1a->count = 0;
+ x1a->tbl = (x1node*)malloc(
+ (sizeof(x1node) + sizeof(x1node*))*1024 );
+ if( x1a->tbl==0 ){
+ free(x1a);
+ x1a = 0;
+ }else{
+ int i;
+ x1a->ht = (x1node**)&(x1a->tbl[1024]);
+ for(i=0; i<1024; i++) x1a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(data)
+char *data;
+{
+ x1node *np;
+ int h;
+ int ph;
+
+ if( x1a==0 ) return 0;
+ ph = strhash(data);
+ h = ph & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x1a->count>=x1a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x1 array;
+ array.size = size = x1a->size*2;
+ array.count = x1a->count;
+ array.tbl = (x1node*)malloc(
+ (sizeof(x1node) + sizeof(x1node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x1node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x1a->count; i++){
+ x1node *oldnp, *newnp;
+ oldnp = &(x1a->tbl[i]);
+ h = strhash(oldnp->data) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x1a->tbl);
+ *x1a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x1a->size-1);
+ np = &(x1a->tbl[x1a->count++]);
+ np->data = data;
+ if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+ np->next = x1a->ht[h];
+ x1a->ht[h] = np;
+ np->from = &(x1a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+char *Strsafe_find(key)
+char *key;
+{
+ int h;
+ x1node *np;
+
+ if( x1a==0 ) return 0;
+ h = strhash(key) & (x1a->size-1);
+ np = x1a->ht[h];
+ while( np ){
+ if( strcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(x)
+char *x;
+{
+ struct symbol *sp;
+
+ sp = Symbol_find(x);
+ if( sp==0 ){
+ sp = (struct symbol *)malloc( sizeof(struct symbol) );
+ MemoryCheck(sp);
+ sp->name = Strsafe(x);
+ sp->type = isupper(*x) ? TERMINAL : NONTERMINAL;
+ sp->rule = 0;
+ sp->fallback = 0;
+ sp->prec = -1;
+ sp->assoc = UNK;
+ sp->firstset = 0;
+ sp->lambda = B_FALSE;
+ sp->destructor = 0;
+ sp->datatype = 0;
+ Symbol_insert(sp,sp->name);
+ }
+ return sp;
+}
+
+/* Compare two symbols for working purposes
+**
+** Symbols that begin with upper case letters (terminals or tokens)
+** must sort before symbols that begin with lower case letters
+** (non-terminals). Other than that, the order does not matter.
+**
+** We find experimentally that leaving the symbols in their original
+** order (the order they appeared in the grammar file) gives the
+** smallest parser tables in SQLite.
+*/
+int Symbolcmpp(struct symbol **a, struct symbol **b){
+ int i1 = (**a).index + 10000000*((**a).name[0]>'Z');
+ int i2 = (**b).index + 10000000*((**b).name[0]>'Z');
+ return i1-i2;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x2node *tbl; /* The data stored here */
+ struct s_x2node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+ struct symbol *data; /* The data */
+ char *key; /* The key */
+ struct s_x2node *next; /* Next entry with the same hash */
+ struct s_x2node **from; /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(){
+ if( x2a ) return;
+ x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+ if( x2a ){
+ x2a->size = 128;
+ x2a->count = 0;
+ x2a->tbl = (x2node*)malloc(
+ (sizeof(x2node) + sizeof(x2node*))*128 );
+ if( x2a->tbl==0 ){
+ free(x2a);
+ x2a = 0;
+ }else{
+ int i;
+ x2a->ht = (x2node**)&(x2a->tbl[128]);
+ for(i=0; i<128; i++) x2a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(data,key)
+struct symbol *data;
+char *key;
+{
+ x2node *np;
+ int h;
+ int ph;
+
+ if( x2a==0 ) return 0;
+ ph = strhash(key);
+ h = ph & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x2a->count>=x2a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x2 array;
+ array.size = size = x2a->size*2;
+ array.count = x2a->count;
+ array.tbl = (x2node*)malloc(
+ (sizeof(x2node) + sizeof(x2node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x2node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x2a->count; i++){
+ x2node *oldnp, *newnp;
+ oldnp = &(x2a->tbl[i]);
+ h = strhash(oldnp->key) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x2a->tbl);
+ *x2a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x2a->size-1);
+ np = &(x2a->tbl[x2a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+ np->next = x2a->ht[h];
+ x2a->ht[h] = np;
+ np->from = &(x2a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct symbol *Symbol_find(key)
+char *key;
+{
+ int h;
+ x2node *np;
+
+ if( x2a==0 ) return 0;
+ h = strhash(key) & (x2a->size-1);
+ np = x2a->ht[h];
+ while( np ){
+ if( strcmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return the n-th data. Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(n)
+int n;
+{
+ struct symbol *data;
+ if( x2a && n>0 && n<=x2a->count ){
+ data = x2a->tbl[n-1].data;
+ }else{
+ data = 0;
+ }
+ return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+ return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+ struct symbol **array;
+ int i,size;
+ if( x2a==0 ) return 0;
+ size = x2a->count;
+ array = (struct symbol **)malloc( sizeof(struct symbol *)*size );
+ if( array ){
+ for(i=0; i<size; i++) array[i] = x2a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Compare two configurations */
+int Configcmp(a,b)
+struct config *a;
+struct config *b;
+{
+ int x;
+ x = a->rp->index - b->rp->index;
+ if( x==0 ) x = a->dot - b->dot;
+ return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(a,b)
+struct config *a;
+struct config *b;
+{
+ int rc;
+ for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){
+ rc = a->rp->index - b->rp->index;
+ if( rc==0 ) rc = a->dot - b->dot;
+ }
+ if( rc==0 ){
+ if( a ) rc = 1;
+ if( b ) rc = -1;
+ }
+ return rc;
+}
+
+/* Hash a state */
+PRIVATE int statehash(a)
+struct config *a;
+{
+ int h=0;
+ while( a ){
+ h = h*571 + a->rp->index*37 + a->dot;
+ a = a->bp;
+ }
+ return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+ struct state *new;
+ new = (struct state *)malloc( sizeof(struct state) );
+ MemoryCheck(new);
+ return new;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x3node *tbl; /* The data stored here */
+ struct s_x3node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+ struct state *data; /* The data */
+ struct config *key; /* The key */
+ struct s_x3node *next; /* Next entry with the same hash */
+ struct s_x3node **from; /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(){
+ if( x3a ) return;
+ x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+ if( x3a ){
+ x3a->size = 128;
+ x3a->count = 0;
+ x3a->tbl = (x3node*)malloc(
+ (sizeof(x3node) + sizeof(x3node*))*128 );
+ if( x3a->tbl==0 ){
+ free(x3a);
+ x3a = 0;
+ }else{
+ int i;
+ x3a->ht = (x3node**)&(x3a->tbl[128]);
+ for(i=0; i<128; i++) x3a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(data,key)
+struct state *data;
+struct config *key;
+{
+ x3node *np;
+ int h;
+ int ph;
+
+ if( x3a==0 ) return 0;
+ ph = statehash(key);
+ h = ph & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x3a->count>=x3a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x3 array;
+ array.size = size = x3a->size*2;
+ array.count = x3a->count;
+ array.tbl = (x3node*)malloc(
+ (sizeof(x3node) + sizeof(x3node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x3node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x3a->count; i++){
+ x3node *oldnp, *newnp;
+ oldnp = &(x3a->tbl[i]);
+ h = statehash(oldnp->key) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->key = oldnp->key;
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x3a->tbl);
+ *x3a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x3a->size-1);
+ np = &(x3a->tbl[x3a->count++]);
+ np->key = key;
+ np->data = data;
+ if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+ np->next = x3a->ht[h];
+ x3a->ht[h] = np;
+ np->from = &(x3a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct state *State_find(key)
+struct config *key;
+{
+ int h;
+ x3node *np;
+
+ if( x3a==0 ) return 0;
+ h = statehash(key) & (x3a->size-1);
+ np = x3a->ht[h];
+ while( np ){
+ if( statecmp(np->key,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc. Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof()
+{
+ struct state **array;
+ int i,size;
+ if( x3a==0 ) return 0;
+ size = x3a->count;
+ array = (struct state **)malloc( sizeof(struct state *)*size );
+ if( array ){
+ for(i=0; i<size; i++) array[i] = x3a->tbl[i].data;
+ }
+ return array;
+}
+
+/* Hash a configuration */
+PRIVATE int confighash(a)
+struct config *a;
+{
+ int h=0;
+ h = h*571 + a->rp->index*37 + a->dot;
+ return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+ int size; /* The number of available slots. */
+ /* Must be a power of 2 greater than or */
+ /* equal to 1 */
+ int count; /* Number of currently slots filled */
+ struct s_x4node *tbl; /* The data stored here */
+ struct s_x4node **ht; /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+ struct config *data; /* The data */
+ struct s_x4node *next; /* Next entry with the same hash */
+ struct s_x4node **from; /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(){
+ if( x4a ) return;
+ x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+ if( x4a ){
+ x4a->size = 64;
+ x4a->count = 0;
+ x4a->tbl = (x4node*)malloc(
+ (sizeof(x4node) + sizeof(x4node*))*64 );
+ if( x4a->tbl==0 ){
+ free(x4a);
+ x4a = 0;
+ }else{
+ int i;
+ x4a->ht = (x4node**)&(x4a->tbl[64]);
+ for(i=0; i<64; i++) x4a->ht[i] = 0;
+ }
+ }
+}
+/* Insert a new record into the array. Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(data)
+struct config *data;
+{
+ x4node *np;
+ int h;
+ int ph;
+
+ if( x4a==0 ) return 0;
+ ph = confighash(data);
+ h = ph & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp(np->data,data)==0 ){
+ /* An existing entry with the same key is found. */
+ /* Fail because overwrite is not allows. */
+ return 0;
+ }
+ np = np->next;
+ }
+ if( x4a->count>=x4a->size ){
+ /* Need to make the hash table bigger */
+ int i,size;
+ struct s_x4 array;
+ array.size = size = x4a->size*2;
+ array.count = x4a->count;
+ array.tbl = (x4node*)malloc(
+ (sizeof(x4node) + sizeof(x4node*))*size );
+ if( array.tbl==0 ) return 0; /* Fail due to malloc failure */
+ array.ht = (x4node**)&(array.tbl[size]);
+ for(i=0; i<size; i++) array.ht[i] = 0;
+ for(i=0; i<x4a->count; i++){
+ x4node *oldnp, *newnp;
+ oldnp = &(x4a->tbl[i]);
+ h = confighash(oldnp->data) & (size-1);
+ newnp = &(array.tbl[i]);
+ if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+ newnp->next = array.ht[h];
+ newnp->data = oldnp->data;
+ newnp->from = &(array.ht[h]);
+ array.ht[h] = newnp;
+ }
+ free(x4a->tbl);
+ *x4a = array;
+ }
+ /* Insert the new data */
+ h = ph & (x4a->size-1);
+ np = &(x4a->tbl[x4a->count++]);
+ np->data = data;
+ if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+ np->next = x4a->ht[h];
+ x4a->ht[h] = np;
+ np->from = &(x4a->ht[h]);
+ return 1;
+}
+
+/* Return a pointer to data assigned to the given key. Return NULL
+** if no such key. */
+struct config *Configtable_find(key)
+struct config *key;
+{
+ int h;
+ x4node *np;
+
+ if( x4a==0 ) return 0;
+ h = confighash(key) & (x4a->size-1);
+ np = x4a->ht[h];
+ while( np ){
+ if( Configcmp(np->data,key)==0 ) break;
+ np = np->next;
+ }
+ return np ? np->data : 0;
+}
+
+/* Remove all data from the table. Pass each data to the function "f"
+** as it is removed. ("f" may be null to avoid this step.) */
+void Configtable_clear(f)
+int(*f)(/* struct config * */);
+{
+ int i;
+ if( x4a==0 || x4a->count==0 ) return;
+ if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+ for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+ x4a->count = 0;
+ return;
+}
diff --git a/src/lempar.c b/src/lempar.c
new file mode 100644
index 00000000..ee1edbfa
--- /dev/null
+++ b/src/lempar.c
@@ -0,0 +1,687 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is include which follows the "include" declaration
+** in the input file. */
+#include <stdio.h>
+%%
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/*
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands.
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+%%
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+** YYCODETYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 terminals
+** and nonterminals. "int" is used otherwise.
+** YYNOCODE is a number of type YYCODETYPE which corresponds
+** to no legal terminal or nonterminal number. This
+** number is used to fill in empty slots of the hash
+** table.
+** YYFALLBACK If defined, this indicates that one or more tokens
+** have fall-back values which should be used if the
+** original value of the token will not parse.
+** YYACTIONTYPE is the data type used for storing terminal
+** and nonterminal numbers. "unsigned char" is
+** used if there are fewer than 250 rules and
+** states combined. "int" is used otherwise.
+** ParseTOKENTYPE is the data type used for minor tokens given
+** directly to the parser from the tokenizer.
+** YYMINORTYPE is the data type used for all minor tokens.
+** This is typically a union of many types, one of
+** which is ParseTOKENTYPE. The entry in the union
+** for base tokens is called "yy0".
+** YYSTACKDEPTH is the maximum depth of the parser's stack.
+** ParseARG_SDECL A static variable declaration for the %extra_argument
+** ParseARG_PDECL A parameter declaration for the %extra_argument
+** ParseARG_STORE Code to store %extra_argument into yypParser
+** ParseARG_FETCH Code to extract %extra_argument from yypParser
+** YYNSTATE the combined number of states.
+** YYNRULE the number of rules in the grammar
+** YYERRORSYMBOL is the code number of the error symbol. If not
+** defined, then do no error processing.
+*/
+%%
+#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token. These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.
+**
+** Suppose the action integer is N. Then the action is determined as
+** follows
+**
+** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
+** token onto the stack and goto state N.
+**
+** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
+**
+** N == YYNSTATE+YYNRULE A syntax error has occurred.
+**
+** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
+**
+** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
+** slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+** yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol. If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+** yy_action[] A single table containing all actions.
+** yy_lookahead[] A table containing the lookahead for each entry in
+** yy_action. Used to detect hash collisions.
+** yy_shift_ofst[] For each state, the offset into yy_action for
+** shifting terminals.
+** yy_reduce_ofst[] For each state, the offset into yy_action for
+** shifting non-terminals after a reduce.
+** yy_default[] Default action for each state.
+*/
+%%
+#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens. If a construct
+** like the following:
+**
+** %fallback ID X Y Z.
+**
+** appears in the grammer, then ID becomes a fallback token for X, Y,
+** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+%%
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack. Information stored includes:
+**
+** + The state number for the parser at this level of the stack.
+**
+** + The value of the token stored at this level of the stack.
+** (In other words, the "major" token.)
+**
+** + The semantic value stored at this level of the stack. This is
+** the information used by the action routines in the grammar.
+** It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+ int stateno; /* The state-number */
+ int major; /* The major token value. This is the code
+ ** number for the token at this stack level */
+ YYMINORTYPE minor; /* The user-supplied minor token value. This
+ ** is the value of the token */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+ int yyidx; /* Index of top element in stack */
+ int yyerrcnt; /* Shifts left before out of the error */
+ ParseARG_SDECL /* A place to hold %extra_argument */
+ yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/*
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message. Tracing is turned off
+** by making either argument NULL
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+** If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+** line of trace output. If NULL, then tracing is
+** turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+ yyTraceFILE = TraceFILE;
+ yyTracePrompt = zTracePrompt;
+ if( yyTraceFILE==0 ) yyTracePrompt = 0;
+ else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required. The following table supplies these names */
+static const char *yyTokenName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *yyRuleName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+/*
+** This function returns the symbolic name associated with a token
+** value.
+*/
+const char *ParseTokenName(int tokenType){
+#ifndef NDEBUG
+ if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){
+ return yyTokenName[tokenType];
+ }else{
+ return "Unknown";
+ }
+#else
+ return "";
+#endif
+}
+
+/*
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser. This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(size_t)){
+ yyParser *pParser;
+ pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+ if( pParser ){
+ pParser->yyidx = -1;
+ }
+ return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol. The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
+ switch( yymajor ){
+ /* Here is inserted the actions which take place when a
+ ** terminal or non-terminal is destroyed. This can happen
+ ** when the symbol is popped from the stack during a
+ ** reduce or during error processing or when a parser is
+ ** being destroyed before it is finished parsing.
+ **
+ ** Note: during a reduce, the only symbols destroyed are those
+ ** which appear on the RHS of the rule, but which are not used
+ ** inside the C code.
+ */
+%%
+ default: break; /* If no destructor action specified: do nothing */
+ }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+ YYCODETYPE yymajor;
+ yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+ if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+ if( yyTraceFILE && pParser->yyidx>=0 ){
+ fprintf(yyTraceFILE,"%sPopping %s\n",
+ yyTracePrompt,
+ yyTokenName[yytos->major]);
+ }
+#endif
+ yymajor = yytos->major;
+ yy_destructor( yymajor, &yytos->minor);
+ pParser->yyidx--;
+ return yymajor;
+}
+
+/*
+** Deallocate and destroy a parser. Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser. This should be a pointer
+** obtained from ParseAlloc.
+** <li> A pointer to a function used to reclaim memory obtained
+** from malloc.
+** </ul>
+*/
+void ParseFree(
+ void *p, /* The parser to be deleted */
+ void (*freeProc)(void*) /* Function used to reclaim memory */
+){
+ yyParser *pParser = (yyParser*)p;
+ if( pParser==0 ) return;
+ while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+ (*freeProc)((void*)pParser);
+}
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */
+ i = yy_shift_ofst[stateno];
+ if( i==YY_SHIFT_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+ int iFallback; /* Fallback token */
+ if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+ && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+ yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+ }
+#endif
+ return yy_find_shift_action(pParser, iFallback);
+ }
+#endif
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead. If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+ yyParser *pParser, /* The parser */
+ int iLookAhead /* The look-ahead token */
+){
+ int i;
+ int stateno = pParser->yystack[pParser->yyidx].stateno;
+
+ i = yy_reduce_ofst[stateno];
+ if( i==YY_REDUCE_USE_DFLT ){
+ return yy_default[stateno];
+ }
+ if( iLookAhead==YYNOCODE ){
+ return YY_NO_ACTION;
+ }
+ i += iLookAhead;
+ if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+ return yy_default[stateno];
+ }else{
+ return yy_action[i];
+ }
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+ yyParser *yypParser, /* The parser to be shifted */
+ int yyNewState, /* The new state to shift in */
+ int yyMajor, /* The major token to shift in */
+ YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
+){
+ yyStackEntry *yytos;
+ yypParser->yyidx++;
+ if( yypParser->yyidx>=YYSTACKDEPTH ){
+ ParseARG_FETCH;
+ yypParser->yyidx--;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will execute if the parser
+ ** stack every overflows */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+ return;
+ }
+ yytos = &yypParser->yystack[yypParser->yyidx];
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
+ yytos->minor = *yypMinor;
+#ifndef NDEBUG
+ if( yyTraceFILE && yypParser->yyidx>0 ){
+ int i;
+ fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+ fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+ for(i=1; i<=yypParser->yyidx; i++)
+ fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+ fprintf(yyTraceFILE,"\n");
+ }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static struct {
+ YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
+ unsigned char nrhs; /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+%%
+};
+
+static void yy_accept(yyParser*); /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+ yyParser *yypParser, /* The parser */
+ int yyruleno /* Number of the rule by which to reduce */
+){
+ int yygoto; /* The next state */
+ int yyact; /* The next action */
+ YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
+ yyStackEntry *yymsp; /* The top of the parser's stack */
+ int yysize; /* Amount to pop the stack */
+ ParseARG_FETCH;
+ yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+ if( yyTraceFILE && yyruleno>=0
+ && yyruleno<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){
+ fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+ yyRuleName[yyruleno]);
+ }
+#endif /* NDEBUG */
+
+ switch( yyruleno ){
+ /* Beginning here are the reduction cases. A typical example
+ ** follows:
+ ** case 0:
+ ** #line <lineno> <grammarfile>
+ ** { ... } // User supplied code
+ ** #line <lineno> <thisfile>
+ ** break;
+ */
+%%
+ };
+ yygoto = yyRuleInfo[yyruleno].lhs;
+ yysize = yyRuleInfo[yyruleno].nrhs;
+ yypParser->yyidx -= yysize;
+ yyact = yy_find_reduce_action(yypParser,yygoto);
+ if( yyact < YYNSTATE ){
+ yy_shift(yypParser,yyact,yygoto,&yygotominor);
+ }else if( yyact == YYNSTATE + YYNRULE + 1 ){
+ yy_accept(yypParser);
+ }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+static void yy_parse_failed(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser fails */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+ yyParser *yypParser, /* The parser */
+ int yymajor, /* The major type of the error token */
+ YYMINORTYPE yyminor /* The minor type of the error token */
+){
+ ParseARG_FETCH;
+#define TOKEN (yyminor.yy0)
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+ yyParser *yypParser /* The parser */
+){
+ ParseARG_FETCH;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+ }
+#endif
+ while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+ /* Here code is inserted which will be executed whenever the
+ ** parser accepts */
+%%
+ ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number. The third is
+** the minor token. The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+ void *yyp, /* The parser */
+ int yymajor, /* The major token code number */
+ ParseTOKENTYPE yyminor /* The value for the token */
+ ParseARG_PDECL /* Optional %extra_argument parameter */
+){
+ YYMINORTYPE yyminorunion;
+ int yyact; /* The parser action. */
+ int yyendofinput; /* True if we are at the end of input */
+ int yyerrorhit = 0; /* True if yymajor has invoked an error */
+ yyParser *yypParser; /* The parser */
+
+ /* (re)initialize the parser, if necessary */
+ yypParser = (yyParser*)yyp;
+ if( yypParser->yyidx<0 ){
+ if( yymajor==0 ) return;
+ yypParser->yyidx = 0;
+ yypParser->yyerrcnt = -1;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+ }
+ yyminorunion.yy0 = yyminor;
+ yyendofinput = (yymajor==0);
+ ParseARG_STORE;
+
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+
+ do{
+ yyact = yy_find_shift_action(yypParser,yymajor);
+ if( yyact<YYNSTATE ){
+ yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+ yypParser->yyerrcnt--;
+ if( yyendofinput && yypParser->yyidx>=0 ){
+ yymajor = 0;
+ }else{
+ yymajor = YYNOCODE;
+ }
+ }else if( yyact < YYNSTATE + YYNRULE ){
+ yy_reduce(yypParser,yyact-YYNSTATE);
+ }else if( yyact == YY_ERROR_ACTION ){
+ int yymx;
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+ }
+#endif
+#ifdef YYERRORSYMBOL
+ /* A syntax error has occurred.
+ ** The response to an error depends upon whether or not the
+ ** grammar defines an error token "ERROR".
+ **
+ ** This is what we do if the grammar does define ERROR:
+ **
+ ** * Call the %syntax_error function.
+ **
+ ** * Begin popping the stack until we enter a state where
+ ** it is legal to shift the error symbol, then shift
+ ** the error symbol.
+ **
+ ** * Set the error count to three.
+ **
+ ** * Begin accepting and shifting new tokens. No new error
+ ** processing will occur until three tokens have been
+ ** shifted successfully.
+ **
+ */
+ if( yypParser->yyerrcnt<0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yymx = yypParser->yystack[yypParser->yyidx].major;
+ if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+ if( yyTraceFILE ){
+ fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+ yyTracePrompt,yyTokenName[yymajor]);
+ }
+#endif
+ yy_destructor(yymajor,&yyminorunion);
+ yymajor = YYNOCODE;
+ }else{
+ while(
+ yypParser->yyidx >= 0 &&
+ yymx != YYERRORSYMBOL &&
+ (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE
+ ){
+ yy_pop_parser_stack(yypParser);
+ }
+ if( yypParser->yyidx < 0 || yymajor==0 ){
+ yy_destructor(yymajor,&yyminorunion);
+ yy_parse_failed(yypParser);
+ yymajor = YYNOCODE;
+ }else if( yymx!=YYERRORSYMBOL ){
+ YYMINORTYPE u2;
+ u2.YYERRSYMDT = 0;
+ yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+ }
+ }
+ yypParser->yyerrcnt = 3;
+ yyerrorhit = 1;
+#else /* YYERRORSYMBOL is not defined */
+ /* This is what we do if the grammar does not define ERROR:
+ **
+ ** * Report an error message, and throw away the input token.
+ **
+ ** * If the input token is $, then fail the parse.
+ **
+ ** As before, subsequent error messages are suppressed until
+ ** three input tokens have been successfully shifted.
+ */
+ if( yypParser->yyerrcnt<=0 ){
+ yy_syntax_error(yypParser,yymajor,yyminorunion);
+ }
+ yypParser->yyerrcnt = 3;
+ yy_destructor(yymajor,&yyminorunion);
+ if( yyendofinput ){
+ yy_parse_failed(yypParser);
+ }
+ yymajor = YYNOCODE;
+#endif
+ }else{
+ yy_accept(yypParser);
+ yymajor = YYNOCODE;
+ }
+ }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+ return;
+}
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 00000000..7d7a3331
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,238 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "config.h"
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+#include "log.h"
+#include "array.h"
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+
+/**
+ * open the errorlog
+ *
+ * if the open failed, report to the user and die
+ * if no filename is given, use syslog instead
+ *
+ */
+
+int log_error_open(server *srv) {
+ int fd;
+ int close_stderr = 1;
+
+ if (srv->srvconf.error_logfile->used) {
+ const char *logfile = srv->srvconf.error_logfile->ptr;
+
+ if (-1 == (srv->log_error_fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+ log_error_write(srv, __FILE__, __LINE__, "SSSS",
+ "opening errorlog '", logfile,
+ "' failed: ", strerror(errno));
+
+ return -1;
+ }
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(srv->log_error_fd, F_SETFD, FD_CLOEXEC);
+#endif
+ } else {
+ srv->log_error_fd = -1;
+#ifdef HAVE_SYSLOG_H
+ /* syslog-mode */
+ srv->log_using_syslog = 1;
+ openlog("lighttpd", LOG_CONS, LOG_LOCAL0);
+#endif
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "server started");
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ /* don't close stderr for debugging purposes if run in valgrind */
+ if (RUNNING_ON_VALGRIND) close_stderr = 0;
+#endif
+
+ /* move stderr to /dev/null */
+ if (close_stderr &&
+ -1 != (fd = open("/dev/null", O_WRONLY))) {
+ close(STDERR_FILENO);
+ dup2(fd, STDERR_FILENO);
+ close(fd);
+ }
+ return 0;
+}
+
+/**
+ * open the errorlog
+ *
+ * if the open failed, report to the user and die
+ * if no filename is given, use syslog instead
+ *
+ */
+
+int log_error_cycle(server *srv) {
+ /* only cycle if we are not in syslog-mode */
+
+ if (0 == srv->log_using_syslog) {
+ const char *logfile = srv->srvconf.error_logfile->ptr;
+ /* already check of opening time */
+
+ int new_fd;
+
+ if (-1 == (new_fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+ /* write to old log */
+ log_error_write(srv, __FILE__, __LINE__, "SSSSS",
+ "cycling errorlog '", logfile,
+ "' failed: ", strerror(errno),
+ ", falling back to syslog()");
+
+ close(srv->log_error_fd);
+ srv->log_error_fd = -1;
+#ifdef HAVE_SYSLOG_H
+ /* fall back to syslog() */
+ srv->log_using_syslog = 1;
+
+ openlog("lighttpd", LOG_CONS, LOG_LOCAL0);
+#endif
+ } else {
+ /* ok, new log is open, close the old one */
+ close(srv->log_error_fd);
+ srv->log_error_fd = new_fd;
+ }
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "logfiles cycled");
+
+ return 0;
+}
+
+int log_error_close(server *srv) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "server stopped");
+
+ if (srv->log_error_fd >= 0) {
+ close(srv->log_error_fd);
+ } else if(srv->log_using_syslog) {
+#ifdef HAVE_SYSLOG_H
+ closelog();
+#endif
+ }
+
+ return 0;
+}
+
+int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...) {
+ va_list ap;
+
+ if (srv->log_using_syslog == 0) {
+ /* cache the generated timestamp */
+ if (srv->cur_ts != srv->last_generated_debug_ts) {
+ buffer_prepare_copy(srv->ts_debug_str, 255);
+ strftime(srv->ts_debug_str->ptr, srv->ts_debug_str->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(srv->cur_ts)));
+ srv->ts_debug_str->used = strlen(srv->ts_debug_str->ptr) + 1;
+
+ srv->last_generated_debug_ts = srv->cur_ts;
+ }
+
+ buffer_copy_string_buffer(srv->error_log, srv->ts_debug_str);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, ": (");
+ } else {
+ BUFFER_COPY_STRING_CONST(srv->error_log, "(");
+ }
+ buffer_append_string(srv->error_log, filename);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, ".");
+ buffer_append_long(srv->error_log, line);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, ") ");
+
+
+ for(va_start(ap, fmt); *fmt; fmt++) {
+ int d;
+ char *s;
+ buffer *b;
+ off_t o;
+
+ switch(*fmt) {
+ case 's': /* string */
+ s = va_arg(ap, char *);
+ buffer_append_string(srv->error_log, s);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'b': /* buffer */
+ b = va_arg(ap, buffer *);
+ buffer_append_string_buffer(srv->error_log, b);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'd': /* int */
+ d = va_arg(ap, int);
+ buffer_append_long(srv->error_log, d);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'o': /* off_t */
+ o = va_arg(ap, off_t);
+ buffer_append_off_t(srv->error_log, o);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'x': /* int (hex) */
+ d = va_arg(ap, int);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, "0x");
+ buffer_append_hex(srv->error_log, d);
+ BUFFER_APPEND_STRING_CONST(srv->error_log, " ");
+ break;
+ case 'S': /* string */
+ s = va_arg(ap, char *);
+ buffer_append_string(srv->error_log, s);
+ break;
+ case 'B': /* buffer */
+ b = va_arg(ap, buffer *);
+ buffer_append_string_buffer(srv->error_log, b);
+ break;
+ case 'D': /* int */
+ d = va_arg(ap, int);
+ buffer_append_long(srv->error_log, d);
+ break;
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case ',':
+ case ' ':
+ buffer_append_string_len(srv->error_log, fmt, 1);
+ break;
+ }
+ }
+ va_end(ap);
+
+ BUFFER_APPEND_STRING_CONST(srv->error_log, "\n");
+
+ if (srv->log_error_fd >= 0) {
+ write(srv->log_error_fd, srv->error_log->ptr, srv->error_log->used - 1);
+ } else if (srv->log_using_syslog == 0) {
+ /* only available at startup time */
+ write(STDERR_FILENO, srv->error_log->ptr, srv->error_log->used - 1);
+ } else {
+#ifdef HAVE_SYSLOG_H
+ syslog(LOG_ERR, "%s", srv->error_log->ptr);
+#endif
+ }
+
+ return 0;
+}
+
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 00000000..bffee3a2
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,13 @@
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#include "server.h"
+
+#define WP() log_error_write(srv, __FILE__, __LINE__, "");
+
+int log_error_open(server *srv);
+int log_error_close(server *srv);
+int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...);
+int log_error_cycle(server *srv);
+
+#endif
diff --git a/src/md5.c b/src/md5.c
new file mode 100644
index 00000000..5696b71a
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,356 @@
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+#include "config.h"
+
+#ifndef USE_OPENSSL
+#include <string.h>
+
+#include "md5_global.h"
+#include "md5.h"
+
+/* Constants for MD5Transform routine.
+ */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64]));
+static void Encode PROTO_LIST
+ ((unsigned char *, UINT4 *, unsigned int));
+static void Decode PROTO_LIST
+ ((UINT4 *, unsigned char *, unsigned int));
+
+#ifdef HAVE_MEMCPY
+#define MD5_memcpy(output, input, len) memcpy((output), (input), (len))
+#else
+static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int));
+#endif
+#ifdef HAVE_MEMSET
+#define MD5_memset(output, value, len) memset((output), (value), (len))
+#else
+static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int));
+#endif
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void MD5_Init (context)
+MD5_CTX *context; /* context */
+{
+ context->count[0] = context->count[1] = 0;
+ /* Load magic initialization constants.
+*/
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context.
+ */
+void MD5_Update (context, input, inputLen)
+MD5_CTX *context; /* context */
+unsigned char *input; /* input block */
+unsigned int inputLen; /* length of input block */
+{
+ unsigned int i, ndx, partLen;
+
+ /* Compute number of bytes mod 64 */
+ ndx = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4)inputLen << 3))
+
+ < ((UINT4)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4)inputLen >> 29);
+
+ partLen = 64 - ndx;
+
+ /* Transform as many times as possible.
+*/
+ if (inputLen >= partLen) {
+ MD5_memcpy
+ ((POINTER)&context->buffer[ndx], (POINTER)input, partLen);
+ MD5Transform (context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform (context->state, &input[i]);
+
+ ndx = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ MD5_memcpy
+ ((POINTER)&context->buffer[ndx], (POINTER)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context.
+ */
+void MD5_Final (digest, context)
+unsigned char digest[16]; /* message digest */
+MD5_CTX *context; /* context */
+{
+ unsigned char bits[8];
+ unsigned int ndx, padLen;
+
+ /* Save number of bits */
+ Encode (bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.
+*/
+ ndx = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (ndx < 56) ? (56 - ndx) : (120 - ndx);
+ MD5_Update (context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5_Update (context, bits, 8);
+
+ /* Store state in digest */
+ Encode (digest, context->state, 16);
+
+ /* Zeroize sensitive information.
+*/
+ MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform (state, block)
+UINT4 state[4];
+unsigned char block[64];
+{
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode (x, block, 64);
+
+ /* Round 1 */
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information.
+
+*/
+ MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+ a multiple of 4.
+ */
+static void Encode (output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4) {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+ a multiple of 4.
+ */
+static void Decode (output, input, len)
+UINT4 *output;
+unsigned char *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+ (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+#ifndef HAVE_MEMCPY
+static void MD5_memcpy (output, input, len)
+POINTER output;
+POINTER input;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+
+ output[i] = input[i];
+}
+#endif
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+#ifndef HAVE_MEMSET
+static void MD5_memset (output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ ((char *)output)[i] = (char)value;
+}
+#endif
+#endif
diff --git a/src/md5.h b/src/md5.h
new file mode 100644
index 00000000..4b601484
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,37 @@
+/* MD5.H - header file for MD5C.C
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+/* MD5 context. */
+typedef struct {
+ UINT4 state[4]; /* state (ABCD) */
+ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+void MD5_Init PROTO_LIST ((MD5_CTX *));
+void MD5_Update PROTO_LIST
+ ((MD5_CTX *, unsigned char *, unsigned int));
+void MD5_Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
+
diff --git a/src/md5_global.h b/src/md5_global.h
new file mode 100644
index 00000000..6a5c94ff
--- /dev/null
+++ b/src/md5_global.h
@@ -0,0 +1,31 @@
+/* GLOBAL.H - RSAREF types and constants
+ */
+
+/* PROTOTYPES should be set to one if and only if the compiler supports
+ function argument prototyping.
+The following makes PROTOTYPES default to 0 if it has not already
+
+ been defined with C compiler flags.
+ */
+#ifndef PROTOTYPES
+#define PROTOTYPES 0
+#endif
+
+/* POINTER defines a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* UINT2 defines a two byte word */
+typedef unsigned short int UINT2;
+
+/* UINT4 defines a four byte word */
+typedef unsigned long int UINT4;
+
+/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
+If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
+ returns an empty list.
+ */
+#if PROTOTYPES
+#define PROTO_LIST(list) list
+#else
+#define PROTO_LIST(list) ()
+#endif
diff --git a/src/mod_access.c b/src/mod_access.c
new file mode 100644
index 00000000..b2c81cfb
--- /dev/null
+++ b/src/mod_access.c
@@ -0,0 +1,177 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+typedef struct {
+ array *access_deny;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_access_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+FREE_FUNC(mod_access_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->access_deny);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_access_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.access-deny", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->access_deny = array_init();
+
+ cv[0].destination = s->access_deny;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_access_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.access-deny"))) {
+ PATCH(access_deny);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_access_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(access_deny);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_access_uri_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k, i;
+
+ UNUSED(srv);
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_access_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_access_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ s_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.access_deny->used; k++) {
+ data_string *ds = (data_string *)p->conf.access_deny->data[k];
+ int ct_len = ds->value->used - 1;
+
+ if (ct_len > s_len) continue;
+
+ if (ds->value->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
+ con->http_status = 403;
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+
+int mod_access_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("access");
+
+ p->init = mod_access_init;
+ p->set_defaults = mod_access_set_defaults;
+ p->handle_uri_clean = mod_access_uri_handler;
+ p->cleanup = mod_access_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_accesslog.c b/src/mod_accesslog.c
new file mode 100644
index 00000000..22574b12
--- /dev/null
+++ b/src/mod_accesslog.c
@@ -0,0 +1,840 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include <stdio.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+#include "sys-socket.h"
+
+#ifdef HAVE_SYSLOG_H
+# include <syslog.h>
+#endif
+
+typedef struct {
+ char key;
+ enum {
+ FORMAT_UNSET,
+ FORMAT_UNSUPPORTED,
+ FORMAT_PERCENT,
+ FORMAT_REMOTE_HOST,
+ FORMAT_REMOTE_IDENT,
+ FORMAT_REMOTE_USER,
+ FORMAT_TIMESTAMP,
+ FORMAT_REQUEST_LINE,
+ FORMAT_STATUS,
+ FORMAT_BYTES_OUT_NO_HEADER,
+ FORMAT_HEADER,
+
+ FORMAT_REMOTE_ADDR,
+ FORMAT_LOCAL_ADDR,
+ FORMAT_COOKIE,
+ FORMAT_TIME_USED_MS,
+ FORMAT_ENV,
+ FORMAT_FILENAME,
+ FORMAT_REQUEST_PROTOCOL,
+ FORMAT_REQUEST_METHOD,
+ FORMAT_SERVER_PORT,
+ FORMAT_QUERY_STRING,
+ FORMAT_TIME_USED,
+ FORMAT_URL,
+ FORMAT_SERVER_NAME,
+ FORMAT_CONNECTION_STATUS,
+ FORMAT_BYTES_IN,
+ FORMAT_BYTES_OUT,
+
+ FORMAT_RESPONSE_HEADER
+ } type;
+} format_mapping;
+
+/**
+ *
+ *
+ * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
+ *
+ */
+
+const format_mapping fmap[] =
+{
+ { '%', FORMAT_PERCENT },
+ { 'h', FORMAT_REMOTE_HOST },
+ { 'l', FORMAT_REMOTE_IDENT },
+ { 'u', FORMAT_REMOTE_USER },
+ { 't', FORMAT_TIMESTAMP },
+ { 'r', FORMAT_REQUEST_LINE },
+ { 's', FORMAT_STATUS },
+ { 'b', FORMAT_BYTES_OUT_NO_HEADER },
+ { 'i', FORMAT_HEADER },
+
+ { 'a', FORMAT_REMOTE_ADDR },
+ { 'A', FORMAT_LOCAL_ADDR },
+ { 'B', FORMAT_BYTES_OUT_NO_HEADER },
+ { 'C', FORMAT_COOKIE },
+ { 'D', FORMAT_TIME_USED_MS },
+ { 'e', FORMAT_ENV },
+ { 'f', FORMAT_FILENAME },
+ { 'H', FORMAT_REQUEST_PROTOCOL },
+ { 'm', FORMAT_REQUEST_METHOD },
+ { 'n', FORMAT_UNSUPPORTED }, /* we have no notes */
+ { 'p', FORMAT_SERVER_PORT },
+ { 'P', FORMAT_UNSUPPORTED }, /* we are only one process */
+ { 'q', FORMAT_QUERY_STRING },
+ { 'T', FORMAT_TIME_USED },
+ { 'U', FORMAT_URL }, /* w/o querystring */
+ { 'v', FORMAT_SERVER_NAME },
+ { 'V', FORMAT_UNSUPPORTED },
+ { 'X', FORMAT_CONNECTION_STATUS },
+ { 'I', FORMAT_BYTES_IN },
+ { 'O', FORMAT_BYTES_OUT },
+
+ { 'o', FORMAT_RESPONSE_HEADER },
+
+ { '\0', FORMAT_UNSET }
+};
+
+
+typedef struct {
+ enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type;
+
+ buffer *string;
+ int field;
+} format_field;
+
+typedef struct {
+ format_field **ptr;
+
+ size_t used;
+ size_t size;
+} format_fields;
+
+typedef struct {
+ buffer *access_logfile;
+ buffer *format;
+ unsigned short use_syslog;
+
+
+ int log_access_fd;
+ time_t last_generated_accesslog_ts;
+ time_t *last_generated_accesslog_ts_ptr;
+
+
+ buffer *access_logbuffer;
+ buffer *ts_accesslog_str;
+
+ format_fields *parsed_format;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_accesslog_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+int accesslog_parse_format(server *srv, format_fields *fields, buffer *format) {
+ size_t i, j, k = 0, start = 0;
+
+ for (i = 0; i < format->used - 1; i++) {
+
+ switch(format->ptr[i]) {
+ case '%':
+ if (start != i) {
+ /* copy the string */
+ if (fields->size == 0) {
+ fields->size = 16;
+ fields->used = 0;
+ fields->ptr = malloc(fields->size * sizeof(format_fields * ));
+ } else if (fields->used == fields->size) {
+ fields->size += 16;
+ fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * ));
+ }
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_STRING;
+ fields->ptr[fields->used]->string = buffer_init();
+
+ buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
+
+ fields->used++;
+ }
+
+
+ /* we need a new field */
+
+ if (fields->size == 0) {
+ fields->size = 16;
+ fields->used = 0;
+ fields->ptr = malloc(fields->size * sizeof(format_fields * ));
+ } else if (fields->used == fields->size) {
+ fields->size += 16;
+ fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * ));
+ }
+
+ /* search for the terminating command */
+ switch (format->ptr[i+1]) {
+ case '>':
+ case '<':
+ /* only for s */
+
+ for (j = 0; fmap[j].key != '\0'; j++) {
+ if (fmap[j].key != format->ptr[i+2]) continue;
+
+ /* found key */
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_FORMAT;
+ fields->ptr[fields->used]->field = fmap[j].type;
+ fields->ptr[fields->used]->string = NULL;
+
+ fields->used++;
+
+ break;
+ }
+
+ if (fmap[j].key == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ start = i + 3;
+
+ break;
+ case '{':
+ /* go forward to } */
+
+ for (k = i+2; k < format->used - 1; k++) {
+ if (format->ptr[k] == '}') break;
+ }
+
+ if (k == format->used - 1) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+ if (format->ptr[k+1] == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ for (j = 0; fmap[j].key != '\0'; j++) {
+ if (fmap[j].key != format->ptr[k+1]) continue;
+
+ /* found key */
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_FORMAT;
+ fields->ptr[fields->used]->field = fmap[j].type;
+ fields->ptr[fields->used]->string = buffer_init();
+
+ buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + i + 2, k - (i + 2));
+
+ fields->used++;
+
+ break;
+ }
+
+ if (fmap[j].key == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ start = k + 2;
+
+ break;
+ default:
+ for (j = 0; fmap[j].key != '\0'; j++) {
+ if (fmap[j].key != format->ptr[i+1]) continue;
+
+ /* found key */
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_FORMAT;
+ fields->ptr[fields->used]->field = fmap[j].type;
+ fields->ptr[fields->used]->string = NULL;
+
+ fields->used++;
+
+ break;
+ }
+
+ if (fmap[j].key == '\0') {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed");
+ return -1;
+ }
+
+ start = i + 2;
+
+ break;
+ }
+
+ break;
+ }
+ }
+
+ if (start < i) {
+ /* copy the string */
+ if (fields->size == 0) {
+ fields->size = 16;
+ fields->used = 0;
+ fields->ptr = malloc(fields->size * sizeof(format_fields * ));
+ } else if (fields->used == fields->size) {
+ fields->size += 16;
+ fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * ));
+ }
+
+ fields->ptr[fields->used] = malloc(sizeof(format_fields));
+ fields->ptr[fields->used]->type = FIELD_STRING;
+ fields->ptr[fields->used]->string = buffer_init();
+
+ buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start);
+
+ fields->used++;
+ }
+
+ return 0;
+}
+
+FREE_FUNC(mod_accesslog_free) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (s->access_logbuffer->used) {
+ if (s->use_syslog) {
+# ifdef HAVE_SYSLOG_H
+ syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 1, s->access_logbuffer->ptr);
+# endif
+ } else if (s->log_access_fd != -1) {
+ write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
+ }
+ }
+
+ if (s->log_access_fd != -1) close(s->log_access_fd);
+
+ buffer_free(s->ts_accesslog_str);
+ buffer_free(s->access_logbuffer);
+ buffer_free(s->format);
+ buffer_free(s->access_logfile);
+
+ if (s->parsed_format) {
+ size_t j;
+ for (j = 0; j < s->parsed_format->used; j++) {
+ if (s->parsed_format->ptr[j]->string) buffer_free(s->parsed_format->ptr[j]->string);
+ free(s->parsed_format->ptr[j]);
+ }
+ free(s->parsed_format->ptr);
+ free(s->parsed_format);
+ }
+
+ free(s);
+ }
+
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(log_access_open) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "accesslog.filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "accesslog.use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
+ { "accesslog.format", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->access_logfile = buffer_init();
+ s->format = buffer_init();
+ s->access_logbuffer = buffer_init();
+ s->ts_accesslog_str = buffer_init();
+ s->log_access_fd = -1;
+ s->last_generated_accesslog_ts = 0;
+ s->last_generated_accesslog_ts_ptr = &(s->last_generated_accesslog_ts);
+
+
+ cv[0].destination = s->access_logfile;
+ cv[1].destination = &(s->use_syslog);
+ cv[2].destination = s->format;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (i == 0 && buffer_is_empty(s->format)) {
+ /* set a default logfile string */
+
+ buffer_copy_string(s->format, "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"");
+ }
+
+ /* parse */
+
+ if (s->format->used) {
+ s->parsed_format = calloc(1, sizeof(*(s->parsed_format)));
+
+ if (-1 == accesslog_parse_format(srv, s->parsed_format, s->format)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "config: ", "failed");
+ return HANDLER_ERROR;
+ }
+#if 0
+ /* debugging */
+ for (j = 0; j < s->parsed_format->used; j++) {
+ switch (s->parsed_format->ptr[j]->type) {
+ case FIELD_FORMAT:
+ log_error_write(srv, __FILE__, __LINE__, "ssds",
+ "config:", "format", s->parsed_format->ptr[j]->field,
+ s->parsed_format->ptr[j]->string ?
+ s->parsed_format->ptr[j]->string->ptr : "" );
+ break;
+ case FIELD_STRING:
+ log_error_write(srv, __FILE__, __LINE__, "ssbs", "config:", "string '", s->parsed_format->ptr[j]->string, "'");
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ }
+
+ if (s->use_syslog) {
+ if (srv->log_using_syslog == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbs",
+ "accesslog can only be written to syslog if errorlog is also sent to syslog. ABORTING.");
+
+ return HANDLER_ERROR;
+ }
+
+ /* ignore the next checks */
+ continue;
+ }
+
+ if (buffer_is_empty(s->access_logfile)) continue;
+
+ if (s->access_logfile->ptr[0] == '|') {
+#ifdef HAVE_FORK
+ /* create write pipe and spawn process */
+
+ int to_log_fds[2];
+ pid_t pid;
+
+ if (pipe(to_log_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0:
+ /* child */
+
+ close(STDIN_FILENO);
+ dup2(to_log_fds[0], STDIN_FILENO);
+ close(to_log_fds[0]);
+ /* not needed */
+ close(to_log_fds[1]);
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* exec the log-process (skip the | )
+ *
+ */
+
+ execl("/bin/sh", "sh", "-c", s->access_logfile->ptr + 1, NULL);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "spawning log-process failed: ", strerror(errno),
+ s->access_logfile->ptr + 1);
+
+ exit(-1);
+ break;
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno));
+ break;
+ default:
+ close(to_log_fds[0]);
+
+ s->log_access_fd = to_log_fds[1];
+
+ break;
+ }
+#else
+ return -1;
+#endif
+ } else if (-1 == (s->log_access_fd =
+ open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb",
+ "opening access-log failed:",
+ strerror(errno), s->access_logfile);
+
+ return HANDLER_ERROR;
+ }
+ fcntl(s->log_access_fd, F_SETFD, FD_CLOEXEC);
+
+ }
+
+ return HANDLER_GO_ON;
+}
+
+SIGHUP_FUNC(log_access_cycle) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p->config_storage) return HANDLER_GO_ON;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if(s->access_logbuffer->used) {
+ if (s->use_syslog) {
+#ifdef HAVE_SYSLOG_H
+ syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 1, s->access_logbuffer->ptr);
+#endif
+ } else if (s->log_access_fd != -1) {
+ write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1);
+ }
+ s->access_logbuffer->used = 0;
+ }
+
+ if (s->use_syslog == 0 &&
+ s->access_logfile->ptr[0] != '|') {
+
+ close(s->log_access_fd);
+
+ if (-1 == (s->log_access_fd =
+ open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "cycling access-log failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_accesslog_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.filename"))) {
+ PATCH(access_logfile);
+ PATCH(log_access_fd);
+ PATCH(last_generated_accesslog_ts_ptr);
+ PATCH(access_logbuffer);
+ PATCH(ts_accesslog_str);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.format"))) {
+ PATCH(format);
+ PATCH(parsed_format);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.use-syslog"))) {
+ PATCH(use_syslog);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_accesslog_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(access_logfile);
+ PATCH(format);
+ PATCH(log_access_fd);
+ PATCH(last_generated_accesslog_ts_ptr);
+ PATCH(access_logbuffer);
+ PATCH(ts_accesslog_str);
+ PATCH(parsed_format);
+ PATCH(use_syslog);
+
+ return 0;
+}
+#undef PATCH
+
+REQUESTDONE_FUNC(log_access_write) {
+ plugin_data *p = p_d;
+ size_t j, i;
+
+ int newts = 0;
+ data_string *ds;
+
+ mod_accesslog_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_accesslog_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (p->conf.access_logbuffer->used == 0) {
+ buffer_copy_string(p->conf.access_logbuffer, "");
+ }
+
+ for (j = 0; j < p->conf.parsed_format->used; j++) {
+ switch(p->conf.parsed_format->ptr[j]->type) {
+ case FIELD_STRING:
+ buffer_append_string_buffer(p->conf.access_logbuffer, p->conf.parsed_format->ptr[j]->string);
+ break;
+ case FIELD_FORMAT:
+ switch(p->conf.parsed_format->ptr[j]->field) {
+ case FORMAT_TIMESTAMP:
+
+ /* cache the generated timestamp */
+ if (srv->cur_ts != *(p->conf.last_generated_accesslog_ts_ptr)) {
+ struct tm tm;
+#if defined(HAVE_STRUCT_TM_GMTOFF)
+ long scd, hrs, min;
+#endif
+
+ buffer_prepare_copy(p->conf.ts_accesslog_str, 255);
+#if defined(HAVE_STRUCT_TM_GMTOFF)
+# ifdef HAVE_LOCALTIME_R
+ localtime_r(&(srv->cur_ts), &tm);
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S ", &tm);
+# else
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S ", localtime_r(&(srv->cur_ts)));
+# endif
+ p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
+
+ buffer_append_string(p->conf.ts_accesslog_str, tm.tm_gmtoff >= 0 ? "+" : "-");
+
+ scd = abs(tm.tm_gmtoff);
+ hrs = scd / 3600;
+ min = (scd % 3600) / 60;
+
+ /* hours */
+ if (hrs < 10) buffer_append_string(p->conf.ts_accesslog_str, "0");
+ buffer_append_long(p->conf.ts_accesslog_str, hrs);
+
+ if (min < 10) buffer_append_string(p->conf.ts_accesslog_str, "0");
+ buffer_append_long(p->conf.ts_accesslog_str, min);
+ BUFFER_APPEND_STRING_CONST(p->conf.ts_accesslog_str, "]");
+#else
+#ifdef HAVE_GMTIME_R
+ gmtime_r(&(srv->cur_ts), &tm);
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S +0000]", &tm);
+#else
+ strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S +0000]", gmtime(&(srv->cur_ts)));
+#endif
+ p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1;
+#endif
+
+ *(p->conf.last_generated_accesslog_ts_ptr) = srv->cur_ts;
+ newts = 1;
+ }
+
+ buffer_append_string_buffer(p->conf.access_logbuffer, p->conf.ts_accesslog_str);
+
+ break;
+ case FORMAT_REMOTE_HOST:
+
+ /* handle inet_ntop cache */
+
+ buffer_append_string(p->conf.access_logbuffer, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+
+ break;
+ case FORMAT_REMOTE_IDENT:
+ /* ident */
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ break;
+ case FORMAT_REMOTE_USER:
+ if (con->authed_user->used > 1) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->authed_user);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_REQUEST_LINE:
+ if (con->request.request_line->used) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->request.request_line);
+ }
+ break;
+ case FORMAT_STATUS:
+ buffer_append_long(p->conf.access_logbuffer, con->http_status);
+ break;
+
+ case FORMAT_BYTES_OUT_NO_HEADER:
+ if (con->bytes_written > 0) {
+ buffer_append_off_t(p->conf.access_logbuffer,
+ con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_HEADER:
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, ds->value);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_RESPONSE_HEADER:
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, p->conf.parsed_format->ptr[j]->string->ptr))) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, ds->value);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_FILENAME:
+ if (con->physical.path->used > 1) {
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->physical.path);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_BYTES_OUT:
+ if (con->bytes_written > 0) {
+ buffer_append_off_t(p->conf.access_logbuffer, con->bytes_written);
+ } else {
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-");
+ }
+ break;
+ case FORMAT_TIME_USED:
+ buffer_append_long(p->conf.access_logbuffer, srv->cur_ts - con->request_start);
+ break;
+ case FORMAT_SERVER_NAME:
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->server_name);
+ break;
+ case FORMAT_REQUEST_PROTOCOL:
+ buffer_append_string(p->conf.access_logbuffer,
+ con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0");
+ break;
+ case FORMAT_REQUEST_METHOD:
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET: buffer_append_string(p->conf.access_logbuffer, "GET"); break;
+ case HTTP_METHOD_POST: buffer_append_string(p->conf.access_logbuffer, "POST"); break;
+ case HTTP_METHOD_HEAD: buffer_append_string(p->conf.access_logbuffer, "HEAD"); break;
+ default: break;
+ }
+ break;
+ case FORMAT_SERVER_PORT:
+ buffer_append_long(p->conf.access_logbuffer, srv->srvconf.port);
+ break;
+ case FORMAT_QUERY_STRING:
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->uri.query);
+ break;
+ case FORMAT_URL:
+ buffer_append_string_buffer(p->conf.access_logbuffer, con->uri.path_raw);
+ break;
+ case FORMAT_CONNECTION_STATUS:
+ switch(con->keep_alive) {
+ case 0: buffer_append_string(p->conf.access_logbuffer, "-"); break;
+ default: buffer_append_string(p->conf.access_logbuffer, "+"); break;
+ }
+ break;
+ default:
+ /*
+ { 'a', FORMAT_REMOTE_ADDR },
+ { 'A', FORMAT_LOCAL_ADDR },
+ { 'C', FORMAT_COOKIE },
+ { 'D', FORMAT_TIME_USED_MS },
+ { 'e', FORMAT_ENV },
+ { 'I', FORMAT_BYTES_IN },
+ { 'O', FORMAT_BYTES_OUT },
+ */
+
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "\n");
+
+ if (newts || p->conf.access_logbuffer->used > BUFFER_MAX_REUSE_SIZE) {
+ if (p->conf.use_syslog) {
+#ifdef HAVE_SYSLOG_H
+ syslog(LOG_INFO, "%*s", p->conf.access_logbuffer->used - 1, p->conf.access_logbuffer->ptr);
+#endif
+ } else if (p->conf.log_access_fd != -1) {
+ write(p->conf.log_access_fd, p->conf.access_logbuffer->ptr, p->conf.access_logbuffer->used - 1);
+ }
+ buffer_reset(p->conf.access_logbuffer);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_accesslog_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("accesslog");
+
+ p->init = mod_accesslog_init;
+ p->set_defaults= log_access_open;
+ p->cleanup = mod_accesslog_free;
+
+ p->handle_request_done = log_access_write;
+ p->handle_sighup = log_access_cycle;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_alias.c b/src/mod_alias.c
new file mode 100644
index 00000000..501f5fac
--- /dev/null
+++ b/src/mod_alias.c
@@ -0,0 +1,185 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+
+/* plugin config for all request/connections */
+typedef struct {
+ array *alias;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_alias_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_alias_free) {
+ plugin_data *p = p_d;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->alias);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_alias_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "alias.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->alias = array_init();
+ cv[0].destination = s->alias;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_alias_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("alias.url"))) {
+ PATCH(alias);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_alias_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(alias);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_alias_docroot_handler) {
+ plugin_data *p = p_d;
+ int uri_len;
+ size_t k, i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_alias_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_alias_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ uri_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.alias->used; k++) {
+ data_string *ds = (data_string *)p->conf.alias->data[k];
+ int alias_len = ds->key->used - 1;
+
+ if (alias_len > uri_len) continue;
+ if (ds->key->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr, ds->key->ptr, alias_len)) {
+ /* matched */
+
+ buffer_copy_string_buffer(con->physical.doc_root, ds->value);
+ buffer_copy_string(con->physical.rel_path, con->uri.path->ptr + alias_len);
+
+ return HANDLER_GO_ON;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_alias_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("alias");
+
+ p->init = mod_alias_init;
+ p->handle_docroot = mod_alias_docroot_handler;
+ p->set_defaults = mod_alias_set_defaults;
+ p->cleanup = mod_alias_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_auth.c b/src/mod_auth.c
new file mode 100644
index 00000000..824b8f73
--- /dev/null
+++ b/src/mod_auth.c
@@ -0,0 +1,565 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "plugin.h"
+#include "http_auth.h"
+#include "log.h"
+#include "response.h"
+
+
+/**
+ * the basic and digest auth framework
+ *
+ * - config handling
+ * - protocol handling
+ *
+ * http_auth.c
+ * http_auth_digest.c
+ *
+ * do the real work
+ */
+
+INIT_FUNC(mod_auth_init) {
+ mod_auth_plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ p->auth_user = buffer_init();
+#ifdef USE_LDAP
+ p->ldap_filter = buffer_init();
+#endif
+
+ return p;
+}
+
+FREE_FUNC(mod_auth_free) {
+ mod_auth_plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->tmp_buf);
+ buffer_free(p->auth_user);
+#ifdef USE_LDAP
+ buffer_free(p->ldap_filter);
+#endif
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ mod_auth_plugin_config *s = p->config_storage[i];
+
+ array_free(s->auth_require);
+ buffer_free(s->auth_plain_groupfile);
+ buffer_free(s->auth_plain_userfile);
+ buffer_free(s->auth_htdigest_userfile);
+ buffer_free(s->auth_htpasswd_userfile);
+ buffer_free(s->auth_backend_conf);
+
+ buffer_free(s->auth_ldap_hostname);
+ buffer_free(s->auth_ldap_basedn);
+ buffer_free(s->auth_ldap_filter);
+
+#ifdef USE_LDAP
+ buffer_free(s->ldap_filter_pre);
+ buffer_free(s->ldap_filter_post);
+
+ if (s->ldap) ldap_unbind_s(s->ldap);
+#endif
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_auth_patch_connection(server *srv, connection *con, mod_auth_plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ mod_auth_plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend"))) {
+ PATCH(auth_backend);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.plain.groupfile"))) {
+ PATCH(auth_plain_groupfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.plain.userfile"))) {
+ PATCH(auth_plain_userfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.htdigest.userfile"))) {
+ PATCH(auth_htdigest_userfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.htpasswd.userfile"))) {
+ PATCH(auth_htpasswd_userfile);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.require"))) {
+ PATCH(auth_require);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.debug"))) {
+ PATCH(auth_debug);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.hostname"))) {
+ PATCH(auth_ldap_hostname);
+#ifdef USE_LDAP
+ PATCH(ldap);
+ PATCH(ldap_filter_pre);
+ PATCH(ldap_filter_post);
+#endif
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.base-dn"))) {
+ PATCH(auth_ldap_basedn);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.filter"))) {
+ PATCH(auth_ldap_filter);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_auth_setup_connection(server *srv, connection *con, mod_auth_plugin_data *p) {
+ mod_auth_plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(auth_backend);
+ PATCH(auth_plain_groupfile);
+ PATCH(auth_plain_userfile);
+ PATCH(auth_htdigest_userfile);
+ PATCH(auth_htpasswd_userfile);
+ PATCH(auth_require);
+ PATCH(auth_debug);
+ PATCH(auth_ldap_hostname);
+ PATCH(auth_ldap_basedn);
+ PATCH(auth_ldap_filter);
+#ifdef USE_LDAP
+ PATCH(ldap);
+ PATCH(ldap_filter_pre);
+ PATCH(ldap_filter_post);
+#endif
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
+ size_t k;
+ int auth_required = 0, auth_satisfied = 0;
+ char *http_authorization = NULL;
+ data_string *ds;
+ mod_auth_plugin_data *p = p_d;
+ array *req;
+ size_t i;
+
+ /* select the right config */
+ mod_auth_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_auth_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (p->conf.auth_require == NULL) return HANDLER_GO_ON;
+
+ /*
+ * AUTH
+ *
+ */
+
+ /* do we have to ask for auth ? */
+
+ auth_required = 0;
+ auth_satisfied = 0;
+
+ /* search auth-directives for path */
+ for (k = 0; k < p->conf.auth_require->used; k++) {
+ if (p->conf.auth_require->data[k]->key->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr, p->conf.auth_require->data[k]->key->ptr, p->conf.auth_require->data[k]->key->used - 1)) {
+ auth_required = 1;
+ break;
+ }
+ }
+
+ /* nothing to do for us */
+ if (auth_required == 0) return HANDLER_GO_ON;
+
+ req = ((data_array *)(p->conf.auth_require->data[k]))->value;
+
+ /* try to get Authorization-header */
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Authorization"))) {
+ http_authorization = ds->value->ptr;
+ }
+
+ if (ds && ds->value && ds->value->used) {
+ char *auth_realm;
+ data_string *method;
+
+ method = (data_string *)array_get_element(req, "method");
+
+ /* parse auth-header */
+ if (NULL != (auth_realm = strchr(http_authorization, ' '))) {
+ int auth_type_len = auth_realm - http_authorization;
+
+ if ((auth_type_len == 5) &&
+ (0 == strncmp(http_authorization, "Basic", auth_type_len))) {
+
+ if (0 == strcmp(method->value->ptr, "basic")) {
+ auth_satisfied = http_auth_basic_check(srv, con, p, req, con->uri.path, auth_realm+1);
+ }
+ } else if ((auth_type_len == 6) &&
+ (0 == strncmp(http_authorization, "Digest", auth_type_len))) {
+ if (0 == strcmp(method->value->ptr, "digest")) {
+ if (-1 == (auth_satisfied = http_auth_digest_check(srv, con, p, req, con->uri.path, auth_realm+1))) {
+ con->http_status = 400;
+
+ /* a field was missing */
+
+ return HANDLER_FINISHED;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unknown authentification type:",
+ http_authorization);
+ }
+ }
+ }
+
+ if (!auth_satisfied) {
+ data_string *method, *realm;
+ method = (data_string *)array_get_element(req, "method");
+ realm = (data_string *)array_get_element(req, "realm");
+
+ con->http_status = 401;
+
+ if (0 == strcmp(method->value->ptr, "basic")) {
+ buffer_copy_string(p->tmp_buf, "Basic realm=\"");
+ buffer_append_string_buffer(p->tmp_buf, realm->value);
+ buffer_append_string(p->tmp_buf, "\"");
+
+ response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf));
+ } else if (0 == strcmp(method->value->ptr, "digest")) {
+ char hh[33];
+ http_auth_digest_generate_nonce(srv, p, srv->tmp_buf, hh);
+
+ buffer_copy_string(p->tmp_buf, "Digest realm=\"");
+ buffer_append_string_buffer(p->tmp_buf, realm->value);
+ buffer_append_string(p->tmp_buf, "\", nonce=\"");
+ buffer_append_string(p->tmp_buf, hh);
+ buffer_append_string(p->tmp_buf, "\", qop=\"auth\"");
+
+ response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf));
+ } else {
+ /* evil */
+ }
+ return HANDLER_FINISHED;
+ } else {
+ /* the REMOTE_USER header */
+
+ buffer_copy_string_buffer(con->authed_user, p->auth_user);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_auth_set_defaults) {
+ mod_auth_plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "auth.backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "auth.backend.plain.groupfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.plain.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.require", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.hostname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.base-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.ldap.filter", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ mod_auth_plugin_config *s;
+ size_t n;
+ data_array *da;
+ array *ca;
+
+ s = malloc(sizeof(mod_auth_plugin_config));
+ s->auth_plain_groupfile = buffer_init();
+ s->auth_plain_userfile = buffer_init();
+ s->auth_htdigest_userfile = buffer_init();
+ s->auth_htpasswd_userfile = buffer_init();
+ s->auth_backend_conf = buffer_init();
+
+ s->auth_ldap_hostname = buffer_init();
+ s->auth_ldap_basedn = buffer_init();
+ s->auth_ldap_filter = buffer_init();
+ s->auth_debug = 0;
+
+ s->auth_require = array_init();
+
+#ifdef USE_LDAP
+ s->ldap_filter_pre = buffer_init();
+ s->ldap_filter_post = buffer_init();
+ s->ldap = NULL;
+#endif
+
+ cv[0].destination = s->auth_backend_conf;
+ cv[1].destination = s->auth_plain_groupfile;
+ cv[2].destination = s->auth_plain_userfile;
+ cv[3].destination = s->auth_require;
+ cv[4].destination = s->auth_ldap_hostname;
+ cv[5].destination = s->auth_ldap_basedn;
+ cv[6].destination = s->auth_ldap_filter;
+ cv[7].destination = s->auth_htdigest_userfile;
+ cv[8].destination = s->auth_htpasswd_userfile;
+ cv[9].destination = &(s->auth_debug);
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (s->auth_backend_conf->used) {
+ if (0 == strcmp(s->auth_backend_conf->ptr, "htpasswd")) {
+ s->auth_backend = AUTH_BACKEND_HTPASSWD;
+ } else if (0 == strcmp(s->auth_backend_conf->ptr, "htdigest")) {
+ s->auth_backend = AUTH_BACKEND_HTDIGEST;
+ } else if (0 == strcmp(s->auth_backend_conf->ptr, "plain")) {
+ s->auth_backend = AUTH_BACKEND_PLAIN;
+ } else if (0 == strcmp(s->auth_backend_conf->ptr, "ldap")) {
+ s->auth_backend = AUTH_BACKEND_LDAP;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not supported:", s->auth_backend_conf);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ /* no auth.require for this section */
+ if (NULL == (da = (data_array *)array_get_element(ca, "auth.require"))) continue;
+
+ if (da->type != TYPE_ARRAY) continue;
+
+ for (n = 0; n < da->value->used; n++) {
+ size_t m;
+ data_array *da_file = (data_array *)da->value->data[n];
+ const char *method, *realm, *require;
+
+ if (da->value->data[n]->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "auth.require", "[", da->value->data[n]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ method = realm = require = NULL;
+
+ for (m = 0; m < da_file->value->used; m++) {
+ if (da_file->value->data[m]->type == TYPE_STRING) {
+ if (0 == strcmp(da_file->value->data[m]->key->ptr, "method")) {
+ method = ((data_string *)(da_file->value->data[m]))->value->ptr;
+ } else if (0 == strcmp(da_file->value->data[m]->key->ptr, "realm")) {
+ realm = ((data_string *)(da_file->value->data[m]))->value->ptr;
+ } else if (0 == strcmp(da_file->value->data[m]->key->ptr, "require")) {
+ require = ((data_string *)(da_file->value->data[m]))->value->ptr;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "auth.require", "[", da_file->value->data[m]->key, "](string)");
+ return HANDLER_ERROR;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "auth.require", "[", da_file->value->data[m]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (method == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "method", "](string)");
+ return HANDLER_ERROR;
+ }
+
+ if (realm == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "realm", "](string)");
+ return HANDLER_ERROR;
+ }
+
+ if (require == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "require", "](string)");
+ return HANDLER_ERROR;
+ }
+
+ if (method && realm && require) {
+ data_string *ds;
+ data_array *a;
+
+ a = data_array_init();
+ buffer_copy_string_buffer(a->key, da_file->key);
+
+ ds = data_string_init();
+
+ buffer_copy_string(ds->key, "method");
+ buffer_copy_string(ds->value, method);
+
+ array_insert_unique(a->value, (data_unset *)ds);
+
+ ds = data_string_init();
+
+ buffer_copy_string(ds->key, "realm");
+ buffer_copy_string(ds->value, realm);
+
+ array_insert_unique(a->value, (data_unset *)ds);
+
+ ds = data_string_init();
+
+ buffer_copy_string(ds->key, "require");
+ buffer_copy_string(ds->value, require);
+
+ array_insert_unique(a->value, (data_unset *)ds);
+
+ array_insert_unique(s->auth_require, (data_unset *)a);
+ }
+ }
+
+ switch(s->auth_backend) {
+ case AUTH_BACKEND_PLAIN:
+ if (s->auth_plain_userfile->used) {
+ int fd;
+ /* try to read */
+ if (-1 == (fd = open(s->auth_plain_userfile->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening auth.backend.plain.userfile:", s->auth_plain_userfile,
+ "failed:", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+ break;
+ case AUTH_BACKEND_HTPASSWD:
+ if (s->auth_htpasswd_userfile->used) {
+ int fd;
+ /* try to read */
+ if (-1 == (fd = open(s->auth_htpasswd_userfile->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening auth.backend.htpasswd.userfile:", s->auth_htpasswd_userfile,
+ "failed:", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+ break;
+ case AUTH_BACKEND_HTDIGEST:
+ if (s->auth_htdigest_userfile->used) {
+ int fd;
+ /* try to read */
+ if (-1 == (fd = open(s->auth_htdigest_userfile->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "opening auth.backend.htdigest.userfile:", s->auth_htdigest_userfile,
+ "failed:", strerror(errno));
+ return HANDLER_ERROR;
+ }
+ close(fd);
+ }
+ break;
+ case AUTH_BACKEND_LDAP:
+#ifdef USE_LDAP
+#if 0
+ if (s->auth_ldap_basedn->used == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.base-dn has to be set");
+
+ return HANDLER_ERROR;
+ }
+#endif
+
+ if (s->auth_ldap_filter->used) {
+ char *dollar;
+
+ /* parse filter */
+
+ if (NULL == (dollar = strchr(s->auth_ldap_filter->ptr, '$'))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.filter is missing a replace-operator '$'");
+
+ return HANDLER_ERROR;
+ }
+
+ buffer_copy_string_len(s->ldap_filter_pre, s->auth_ldap_filter->ptr, dollar - s->auth_ldap_filter->ptr);
+ buffer_copy_string(s->ldap_filter_post, dollar+1);
+ }
+
+ if (s->auth_ldap_hostname->used) {
+ int ret;
+ if (NULL == (s->ldap = ldap_init(s->auth_ldap_hostname->ptr, LDAP_PORT))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ ret = LDAP_VERSION3;
+ if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(s->ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+
+
+ /* 1. */
+ if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, NULL, NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
+
+ return HANDLER_ERROR;
+ }
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s", "no ldap support available");
+ return HANDLER_ERROR;
+#endif
+ break;
+ default:
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+int mod_auth_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("auth");
+ p->init = mod_auth_init;
+ p->set_defaults = mod_auth_set_defaults;
+ p->handle_uri_clean = mod_auth_uri_handler;
+ p->cleanup = mod_auth_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_auth.h b/src/mod_auth.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/mod_auth.h
diff --git a/src/mod_cgi.c b/src/mod_cgi.c
new file mode 100644
index 00000000..ff91d5f7
--- /dev/null
+++ b/src/mod_cgi.c
@@ -0,0 +1,1191 @@
+#include <sys/types.h>
+#ifdef __WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#endif
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fdevent.h>
+#include <signal.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <stdio.h>
+
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+#include "connections.h"
+#include "joblist.h"
+#include "http_chunk.h"
+
+#include "plugin.h"
+
+enum {EOL_UNSET, EOL_N, EOL_RN};
+
+typedef struct {
+ char **ptr;
+
+ size_t size;
+ size_t used;
+} char_array;
+
+typedef struct {
+ pid_t *ptr;
+ size_t used;
+ size_t size;
+} buffer_pid_t;
+
+typedef struct {
+ array *cgi;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer_pid_t cgi_pid;
+
+ buffer *tmp_buf;
+ buffer *parse_response;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef struct {
+ pid_t pid;
+ int fd;
+ int fde_ndx; /* index into the fd-event buffer */
+
+ connection *remote_conn; /* dumb pointer */
+ plugin_data *plugin_data; /* dumb pointer */
+
+ buffer *response;
+ buffer *response_header;
+} handler_ctx;
+
+static handler_ctx * cgi_handler_ctx_init() {
+ handler_ctx *hctx = calloc(1, sizeof(*hctx));
+
+ assert(hctx);
+
+ hctx->response = buffer_init();
+ hctx->response_header = buffer_init();
+
+ return hctx;
+}
+
+static void cgi_handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+
+ free(hctx);
+}
+
+enum {FDEVENT_HANDLED_UNSET, FDEVENT_HANDLED_FINISHED, FDEVENT_HANDLED_NOT_FINISHED, FDEVENT_HANDLED_ERROR};
+
+INIT_FUNC(mod_cgi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ assert(p);
+
+ p->tmp_buf = buffer_init();
+ p->parse_response = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_cgi_free) {
+ plugin_data *p = p_d;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->cgi);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ if (r->ptr) free(r->ptr);
+
+ buffer_free(p->tmp_buf);
+ buffer_free(p->parse_response);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ assert(s);
+
+ s->cgi = array_init();
+
+ cv[0].destination = s->cgi;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static int cgi_pid_add(server *srv, plugin_data *p, pid_t pid) {
+ int m = -1;
+ size_t i;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] > m) m = r->ptr[i];
+ }
+
+ if (r->size == 0) {
+ r->size = 16;
+ r->ptr = malloc(sizeof(*r->ptr) * r->size);
+ } else if (r->used == r->size) {
+ r->size += 16;
+ r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size);
+ }
+
+ r->ptr[r->used++] = pid;
+
+ return m;
+}
+
+static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) {
+ size_t i;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] == pid) break;
+ }
+
+ if (i != r->used) {
+ /* found */
+
+ if (i != r->used - 1) {
+ r->ptr[i] = r->ptr[r->used - 1];
+ }
+ r->used--;
+ }
+
+ return 0;
+}
+
+static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in, int eol) {
+ char *ns;
+ const char *s;
+ int line = 0;
+
+ UNUSED(srv);
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ for (s = p->parse_response->ptr;
+ NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n')));
+ s = ns + (eol == EOL_RN ? 2 : 1), line++) {
+ const char *key, *value;
+ int key_len;
+ data_string *ds;
+
+ ns[0] = '\0';
+
+ if (line == 0 &&
+ 0 == strncmp(s, "HTTP/1.", 7)) {
+ /* non-parsed header ... we parse them anyway */
+
+ if ((s[7] == '1' ||
+ s[7] == '0') &&
+ s[8] == ' ') {
+ int status;
+ /* after the space should be a status code for us */
+
+ status = strtol(s+9, NULL, 10);
+
+ if (con->http_status >= 100 &&
+ con->http_status < 1000) {
+ /* we expected 3 digits and didn't got them */
+ con->parsed_response |= HTTP_STATUS;
+ con->http_status = status;
+ }
+ }
+ } else {
+
+ key = s;
+ if (NULL == (value = strchr(s, ':'))) {
+ /* we expect: "<key>: <value>\r\n" */
+ continue;
+ }
+
+ key_len = value - key;
+ value += 1;
+
+ /* skip LWS */
+ while (*value == ' ' || *value == '\t') value++;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 6:
+ if (0 == strncasecmp(key, "Status", key_len)) {
+ con->http_status = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_STATUS;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
+ con->parsed_response |= HTTP_CONNECTION;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* CGI/1.1 rev 03 - 7.2.1.2 */
+ if ((con->parsed_response & HTTP_LOCATION) &&
+ !(con->parsed_response & HTTP_STATUS)) {
+ con->http_status = 302;
+ }
+
+ return 0;
+}
+
+
+static int cgi_demux_response(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+
+ while(1) {
+ int n;
+
+ buffer_prepare_copy(hctx->response, 1024);
+ if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) {
+ if (errno == EAGAIN || errno == EINTR) {
+ /* would block, wait for signal */
+ return FDEVENT_HANDLED_NOT_FINISHED;
+ }
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd);
+ return FDEVENT_HANDLED_ERROR;
+ }
+
+ if (n == 0) {
+ /* read finished */
+
+ con->file_finished = 1;
+
+ /* send final chunk */
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+
+ return FDEVENT_HANDLED_FINISHED;
+ }
+
+ hctx->response->ptr[n] = '\0';
+ hctx->response->used = n+1;
+
+ /* split header from body */
+
+ if (con->file_started == 0) {
+ char *c;
+ int in_header = 0;
+ int header_end = 0;
+ int cp, eol = EOL_UNSET;
+ size_t used = 0;
+
+ buffer_append_string_buffer(hctx->response_header, hctx->response);
+
+ /* nph (non-parsed headers) */
+ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1;
+
+ /* search for the \r\n\r\n or \n\n in the string */
+ for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) {
+ if (*c == ':') in_header = 1;
+ else if (*c == '\n') {
+ if (in_header == 0) {
+ /* got a response without a response header */
+
+ c = NULL;
+ header_end = 1;
+ break;
+ }
+
+ if (eol == EOL_UNSET) eol = EOL_N;
+
+ if (*(c+1) == '\n') {
+ header_end = 1;
+ break;
+ }
+
+ } else if (used > 1 && *c == '\r' && *(c+1) == '\n') {
+ if (in_header == 0) {
+ /* got a response without a response header */
+
+ c = NULL;
+ header_end = 1;
+ break;
+ }
+
+ if (eol == EOL_UNSET) eol = EOL_RN;
+
+ if (used > 3 &&
+ *(c+2) == '\r' &&
+ *(c+3) == '\n') {
+ header_end = 1;
+ break;
+ }
+
+ /* skip the \n */
+ c++;
+ cp++;
+ used--;
+ }
+ }
+
+ if (header_end) {
+ if (c == NULL) {
+ /* no header, but a body */
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used);
+ joblist_append(srv, con);
+ } else {
+ size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2);
+ size_t blen = hctx->response_header->used - hlen - 1;
+
+ /* a small hack: terminate after at the second \r */
+ hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1);
+ hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0';
+
+ /* parse the response header */
+ cgi_response_parse(srv, con, p, hctx->response_header, eol);
+
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ if ((hctx->response->used != hlen) && blen > 0) {
+ http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1);
+ joblist_append(srv, con);
+ }
+ }
+
+ con->file_started = 1;
+ }
+ } else {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ }
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr);
+#endif
+ }
+
+ return FDEVENT_HANDLED_NOT_FINISHED;
+}
+
+static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
+ int status;
+ pid_t pid;
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+#ifndef __WIN32
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "emergency exit: cgi",
+ con->fd,
+ hctx->fd);
+#endif
+
+ /* the connection to the browser went away, but we still have a connection
+ * to the CGI script
+ *
+ * close cgi-connection
+ */
+
+ /* close connection to the cgi-script */
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ if (close(hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
+ }
+
+ hctx->fd = -1;
+ hctx->fde_ndx = -1;
+
+ pid = hctx->pid;
+
+ con->plugin_ctx[p->id] = NULL;
+
+ /* is this a good idea ? */
+ cgi_handler_ctx_free(hctx);
+
+ /* if waitpid hasn't been called by response.c yet, do it here */
+ if (pid) {
+ /* check if the CGI-script is already gone */
+ switch(waitpid(pid, &status, WNOHANG)) {
+ case 0:
+ /* not finished yet */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", pid);
+#endif
+ break;
+ case -1:
+ /* */
+ if (errno == EINTR) break;
+
+ /*
+ * errno == ECHILD happens if _subrequest catches the process-status before
+ * we have read the response of the cgi process
+ *
+ * -> catch status
+ * -> WAIT_FOR_EVENT
+ * -> read response
+ * -> we get here with waitpid == ECHILD
+ *
+ */
+ if (errno == ECHILD) return HANDLER_FINISHED;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
+ return HANDLER_ERROR;
+ default:
+ if (WIFEXITED(status)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", pid);
+#endif
+ pid = 0;
+
+ return HANDLER_FINISHED;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid);
+ pid = 0;
+ return HANDLER_FINISHED;
+ }
+ }
+
+
+ kill(pid, SIGTERM);
+
+ /* cgi-script is still alive, queue the PID for removal */
+ cgi_pid_add(srv, p, pid);
+ }
+#endif
+ return HANDLER_FINISHED;
+}
+
+static handler_t cgi_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ return cgi_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+
+static handler_t cgi_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+
+ joblist_append(srv, con);
+
+ if (hctx->fd == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "invalid cgi-fd");
+
+ return HANDLER_ERROR;
+ }
+
+ if (revents & FDEVENT_IN) {
+ switch (cgi_demux_response(srv, hctx)) {
+ case FDEVENT_HANDLED_NOT_FINISHED:
+ break;
+ case FDEVENT_HANDLED_FINISHED:
+ /* we are done */
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "finished");
+#endif
+
+ break;
+ case FDEVENT_HANDLED_ERROR:
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ con->http_status = 500;
+ con->mode = DIRECT;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "demuxer failed: ");
+ break;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ /* nothing to do */
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ /* check if we still have a unfinished header package which is a body in reality */
+ if (con->file_started == 0 &&
+ hctx->response_header->used) {
+ con->file_started = 1;
+ http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used);
+ joblist_append(srv, con);
+ }
+
+ if (con->file_finished == 0) {
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+ }
+
+ con->file_finished = 1;
+
+ if (chunkqueue_is_empty(con->write_queue)) {
+ /* there is nothing left to write */
+ connection_set_state(srv, con, CON_STATE_RESPONSE_END);
+ } else {
+ /* used the write-handler to finish the request on demand */
+
+ }
+
+# if 0
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "got HUP from cgi", con->fd, hctx->fd, revents);
+# endif
+
+ /* rtsigs didn't liked the close */
+ cgi_connection_close(srv, hctx);
+ } else if (revents & FDEVENT_ERR) {
+ con->file_finished = 1;
+
+ /* kill all connections to the cgi process */
+ cgi_connection_close(srv, hctx);
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR");
+#endif
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_FINISHED;
+}
+
+
+static int cgi_env_add(char_array *env, const char *key, size_t key_len, const char *val) {
+ int val_len;
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ val_len = strlen(val);
+
+ dst = malloc(key_len + val_len + 3);
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ /* add the \0 from the value */
+ memcpy(dst + key_len + 1, val, val_len + 1);
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ env->ptr[env->used++] = dst;
+
+ return 0;
+}
+
+static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
+ pid_t pid;
+
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+
+ int to_cgi_fds[2];
+ int from_cgi_fds[2];
+ struct stat st;
+
+#ifndef __WIN32
+
+ /* stat the exec file */
+ if (-1 == (stat(cgi_handler->ptr, &st))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "stat for cgi-handler", cgi_handler,
+ "failed:", strerror(errno));
+ return -1;
+ }
+
+ if (pipe(to_cgi_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
+ return -1;
+ }
+
+ if (pipe(from_cgi_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0: {
+ /* child */
+ char **args;
+ int argc;
+ int i = 0;
+ char buf[32];
+ size_t n;
+ char_array env;
+ char *c;
+ server_socket *srv_sock = con->srv_socket;
+
+ /* move stdout to from_cgi_fd[1] */
+ close(STDOUT_FILENO);
+ dup2(from_cgi_fds[1], STDOUT_FILENO);
+ close(from_cgi_fds[1]);
+ /* not needed */
+ close(from_cgi_fds[0]);
+
+ /* move the stdin to to_cgi_fd[0] */
+ close(STDIN_FILENO);
+ dup2(to_cgi_fds[0], STDIN_FILENO);
+ close(to_cgi_fds[0]);
+ /* not needed */
+ close(to_cgi_fds[1]);
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+#ifdef PACKAGE_NAME
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION);
+#else
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), PACKAGE"/"VERSION);
+#endif
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"),
+ con->server_name->used ?
+ con->server_name->ptr :
+#ifdef HAVE_IPV6
+ inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1)
+#else
+ inet_ntoa(srv_sock->addr.ipv4.sin_addr)
+#endif
+ );
+ cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), "CGI/1.1");
+
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family == AF_INET6 ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+ cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf);
+ cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
+ if (con->request.pathinfo->used) {
+ cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), con->request.pathinfo->ptr);
+ }
+ cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), "200");
+ cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : "");
+
+
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"),
+#ifdef HAVE_IPV6
+ inet_ntop(con->dst_addr.plain.sa_family,
+ con->dst_addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(con->dst_addr.ipv6.sin6_addr) :
+ (const void *) &(con->dst_addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1)
+#else
+ inet_ntoa(con->dst_addr.ipv4.sin_addr)
+#endif
+ );
+
+ if (con->authed_user->used) {
+ cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"),
+ con->authed_user->ptr);
+ }
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf);
+ cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), con->physical.path->ptr);
+ cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), con->uri.path->ptr);
+
+ /* for valgrind */
+ cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), getenv("LD_PRELOAD"));
+ cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), getenv("LD_LIBRARY_PATH"));
+#ifdef __CYGWIN__
+ /* CYGWIN needs SYSTEMROOT */
+ cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), getenv("SYSTEMROOT"));
+#endif
+
+ for (n = 0; n < con->request.headers->used; n++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[n];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+
+ buffer_reset(p->tmp_buf);
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ buffer_copy_string(p->tmp_buf, "HTTP_");
+ p->tmp_buf->used--; /* strip \0 after HTTP_ */
+ }
+
+ buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
+
+ for (j = 0; j < ds->key->used - 1; j++) {
+ p->tmp_buf->ptr[p->tmp_buf->used++] =
+ isalpha((unsigned char)ds->key->ptr[j]) ?
+ toupper((unsigned char)ds->key->ptr[j]) : '_';
+ }
+ p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
+
+ cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), ds->value->ptr);
+ }
+ }
+
+ for (n = 0; n < con->environment->used; n++) {
+ data_string *ds;
+
+ ds = (data_string *)con->environment->data[n];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+
+ buffer_reset(p->tmp_buf);
+
+ buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
+
+ for (j = 0; j < ds->key->used - 1; j++) {
+ p->tmp_buf->ptr[p->tmp_buf->used++] =
+ isalpha((unsigned char)ds->key->ptr[j]) ?
+ toupper((unsigned char)ds->key->ptr[j]) : '_';
+ }
+ p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
+
+ cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), ds->value->ptr);
+ }
+ }
+
+ if (env.size == env.used) {
+ env.size += 16;
+ env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
+ }
+
+ env.ptr[env.used] = NULL;
+
+ /* set up args */
+ argc = 3;
+ args = malloc(sizeof(*args) * argc);
+ i = 0;
+
+ if (cgi_handler->used > 1) {
+ args[i++] = cgi_handler->ptr;
+ }
+ args[i++] = con->physical.path->ptr;
+ args[i++] = NULL;
+
+ /* search for the last / */
+ if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
+ *c = '\0';
+
+ /* change to the physical directory */
+ if (-1 == chdir(con->physical.path->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
+ }
+ *c = '/';
+ }
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ if (i != srv->log_error_fd) close(i);
+ }
+
+ /* exec the cgi */
+ execve(args[0], args, env.ptr);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss", "CGI failed:", strerror(errno), args[0]);
+
+ /* */
+ SEGFAULT();
+ break;
+ }
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
+ break;
+ default: {
+ handler_ctx *hctx;
+ /* father */
+
+ if (con->request.content->used) {
+ write(to_cgi_fds[1], con->request.content->ptr, con->request.content_length);
+ }
+
+ close(from_cgi_fds[1]);
+
+ close(to_cgi_fds[0]);
+ close(to_cgi_fds[1]);
+
+ /* register PID and wait for them asyncronously */
+ con->mode = p->id;
+ buffer_reset(con->physical.path);
+
+ hctx = cgi_handler_ctx_init();
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->pid = pid;
+ hctx->fd = from_cgi_fds[0];
+ hctx->fde_ndx = -1;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ log_error_write(srv, __FILE__, __LINE__, "sd", "cgi close:", hctx->fd);
+
+ close(hctx->fd);
+
+ cgi_handler_ctx_free(hctx);
+
+ con->plugin_ctx[p->id] = NULL;
+
+ return -1;
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.assign"))) {
+ PATCH(cgi);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_cgi_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(cgi);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(cgi_is_handled) {
+ size_t k, s_len, i;
+ plugin_data *p = p_d;
+ buffer *fn = con->physical.path;
+
+ if (fn->used == 0) return HANDLER_ERROR;
+
+ mod_cgi_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_cgi_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ s_len = fn->used - 1;
+
+ for (k = 0; k < p->conf.cgi->used; k++) {
+ data_string *ds = (data_string *)p->conf.cgi->data[k];
+ size_t ct_len = ds->key->used - 1;
+
+ if (ds->key->used == 0) continue;
+ if (s_len < ct_len) continue;
+
+ if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
+ if (cgi_create_env(srv, con, p, ds->value)) {
+ con->http_status = 500;
+
+ buffer_reset(con->physical.path);
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(cgi_trigger) {
+ plugin_data *p = p_d;
+ size_t ndx;
+ /* the trigger handle only cares about lonely PID which we have to wait for */
+#ifndef __WIN32
+
+ for (ndx = 0; ndx < p->cgi_pid.used; ndx++) {
+ int status;
+
+ switch(waitpid(p->cgi_pid.ptr[ndx], &status, WNOHANG)) {
+ case 0:
+ /* not finished yet */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", p->cgi_pid.ptr[ndx]);
+#endif
+ break;
+ case -1:
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
+
+ return HANDLER_ERROR;
+ default:
+
+ if (WIFEXITED(status)) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", p->cgi_pid.ptr[ndx]);
+#endif
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?");
+ }
+
+ cgi_pid_del(srv, p, p->cgi_pid.ptr[ndx]);
+ /* del modified the buffer structure
+ * and copies the last entry to the current one
+ * -> recheck the current index
+ */
+ ndx--;
+ }
+ }
+#endif
+ return HANDLER_GO_ON;
+}
+
+SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
+ int status;
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd", "subrequest, pid =", hctx, hctx->pid);
+#endif
+ if (hctx->pid == 0) return HANDLER_FINISHED;
+#ifndef __WIN32
+ switch(waitpid(hctx->pid, &status, WNOHANG)) {
+ case 0:
+ /* not finished yet */
+ if (con->file_started) {
+ return HANDLER_GO_ON;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ case -1:
+ if (errno == EINTR) return HANDLER_WAIT_FOR_EVENT;
+
+ if (errno == ECHILD && con->file_started == 0) {
+ /*
+ * second round but still not response
+ */
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
+ con->mode = DIRECT;
+ con->http_status = 500;
+
+ hctx->pid = 0;
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ if (close(hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
+ }
+
+ cgi_handler_ctx_free(hctx);
+
+ con->plugin_ctx[p->id] = NULL;
+
+ return HANDLER_FINISHED;
+ default:
+ /* cgi process exited cleanly
+ *
+ * check if we already got the response
+ */
+
+ if (!con->file_started) return HANDLER_WAIT_FOR_EVENT;
+
+ if (WIFEXITED(status)) {
+ /* nothing */
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?");
+
+ con->mode = DIRECT;
+ con->http_status = 500;
+
+ }
+
+ hctx->pid = 0;
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+
+ if (close(hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
+ }
+
+ cgi_handler_ctx_free(hctx);
+
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_FINISHED;
+ }
+#else
+ return HANDLER_ERROR;
+#endif
+}
+
+
+int mod_cgi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("cgi");
+
+ p->handle_connection_close = cgi_connection_close_callback;
+ p->handle_subrequest_start = cgi_is_handled;
+ p->handle_subrequest = mod_cgi_handle_subrequest;
+#if 0
+ p->handle_fdevent = cgi_handle_fdevent;
+#endif
+ p->handle_trigger = cgi_trigger;
+ p->init = mod_cgi_init;
+ p->cleanup = mod_cgi_free;
+ p->set_defaults = mod_fastcgi_set_defaults;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_compress.c b/src/mod_compress.c
new file mode 100644
index 00000000..726d086d
--- /dev/null
+++ b/src/mod_compress.c
@@ -0,0 +1,744 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "response.h"
+
+#include "plugin.h"
+
+#include "crc32.h"
+
+#include "config.h"
+
+#if defined HAVE_ZLIB_H && defined HAVE_LIBZ
+# define USE_ZLIB
+# include <zlib.h>
+#endif
+
+#if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
+# define USE_BZ2LIB
+/* we don't need stdio interface */
+# define BZ_NO_STDIO
+# include <bzlib.h>
+#endif
+
+#include "sys-mmap.h"
+
+/* request: accept-encoding */
+#define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
+#define HTTP_ACCEPT_ENCODING_GZIP BV(1)
+#define HTTP_ACCEPT_ENCODING_DEFLATE BV(2)
+#define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
+#define HTTP_ACCEPT_ENCODING_BZIP2 BV(4)
+
+#ifdef __WIN32
+#define mkdir(x,y) mkdir(x)
+#endif
+
+typedef struct {
+ buffer *compress_cache_dir;
+ array *compress;
+ off_t compress_max_filesize; /** max filesize in kb */
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *ofn;
+ buffer *b;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_compress_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->ofn = buffer_init();
+ p->b = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_compress_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->ofn);
+ buffer_free(p->b);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->compress);
+ buffer_free(s->compress_cache_dir);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_compress_setdefaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+ int ret = HANDLER_GO_ON;
+
+ config_values_t cv[] = {
+ { "compress.cache-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "compress.filetype", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+ { "compress.max-filesize", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->compress_cache_dir = buffer_init();
+ s->compress = array_init();
+ s->compress_max_filesize = 0;
+
+ cv[0].destination = s->compress_cache_dir;
+ cv[1].destination = s->compress;
+ cv[2].destination = &(s->compress_max_filesize);
+
+ p->config_storage[i] = s;
+
+ if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) {
+ ret = HANDLER_ERROR;
+
+ break;
+ }
+
+ ret = HANDLER_GO_ON;
+
+ if (!buffer_is_empty(s->compress_cache_dir)) {
+ struct stat st;
+ if (0 != stat(s->compress_cache_dir->ptr, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "can't stat compress.cache-dir",
+ s->compress_cache_dir, strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ return ret;
+
+}
+
+#ifdef USE_ZLIB
+static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size, time_t mtime) {
+ unsigned char *c;
+ unsigned long crc;
+ z_stream z;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ if (Z_OK != deflateInit2(&z,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ -MAX_WBITS, /* supress zlib-header */
+ 8,
+ Z_DEFAULT_STRATEGY)) {
+ return -1;
+ }
+
+ z.next_in = start;
+ z.avail_in = st_size;
+ z.total_in = 0;
+
+
+ buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12 + 18);
+
+ /* write gzip header */
+
+ c = (unsigned char *)p->b->ptr;
+ c[0] = 0x1f;
+ c[1] = 0x8b;
+ c[2] = Z_DEFLATED;
+ c[3] = 0; /* options */
+ c[4] = (mtime >> 0) & 0xff;
+ c[5] = (mtime >> 8) & 0xff;
+ c[6] = (mtime >> 16) & 0xff;
+ c[7] = (mtime >> 24) & 0xff;
+ c[8] = 0x00; /* extra flags */
+ c[9] = 0x03; /* UNIX */
+
+ p->b->used = 10;
+ z.next_out = (unsigned char *)p->b->ptr + p->b->used;
+ z.avail_out = p->b->size - p->b->used - 8;
+ z.total_out = 0;
+
+ if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
+ deflateEnd(&z);
+ return -1;
+ }
+
+ /* trailer */
+ p->b->used += z.total_out;
+
+ crc = generate_crc32c(start, st_size);
+
+ c = (unsigned char *)p->b->ptr + p->b->used;
+
+ c[0] = (crc >> 0) & 0xff;
+ c[1] = (crc >> 8) & 0xff;
+ c[2] = (crc >> 16) & 0xff;
+ c[3] = (crc >> 24) & 0xff;
+ c[4] = (z.total_in >> 0) & 0xff;
+ c[5] = (z.total_in >> 8) & 0xff;
+ c[6] = (z.total_in >> 16) & 0xff;
+ c[7] = (z.total_in >> 24) & 0xff;
+ p->b->used += 8;
+
+ if (Z_OK != deflateEnd(&z)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int deflate_file_to_buffer_deflate(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
+ z_stream z;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ if (Z_OK != deflateInit2(&z,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ -MAX_WBITS, /* supress zlib-header */
+ 8,
+ Z_DEFAULT_STRATEGY)) {
+ return -1;
+ }
+
+ z.next_in = start;
+ z.avail_in = st_size;
+ z.total_in = 0;
+
+ buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12);
+
+ z.next_out = (unsigned char *)p->b->ptr;
+ z.avail_out = p->b->size;
+ z.total_out = 0;
+
+ if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
+ deflateEnd(&z);
+ return -1;
+ }
+
+ /* trailer */
+ p->b->used += z.total_out;
+
+ if (Z_OK != deflateEnd(&z)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
+
+#ifdef USE_BZ2LIB
+static int deflate_file_to_buffer_bzip2(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
+ bz_stream bz;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ bz.bzalloc = NULL;
+ bz.bzfree = NULL;
+ bz.opaque = NULL;
+
+ if (BZ_OK != BZ2_bzCompressInit(&bz,
+ 9, /* blocksize = 900k */
+ 0, /* no output */
+ 0)) { /* workFactor: default */
+ return -1;
+ }
+
+ bz.next_in = (char *)start;
+ bz.avail_in = st_size;
+ bz.total_in_lo32 = 0;
+ bz.total_in_hi32 = 0;
+
+ buffer_prepare_copy(p->b, (bz.avail_in * 1.1) + 12);
+
+ bz.next_out = p->b->ptr;
+ bz.avail_out = p->b->size;
+ bz.total_out_lo32 = 0;
+ bz.total_out_hi32 = 0;
+
+ if (BZ_STREAM_END != BZ2_bzCompress(&bz, BZ_FINISH)) {
+ BZ2_bzCompressEnd(&bz);
+ return -1;
+ }
+
+ /* file is too large for now */
+ if (bz.total_out_hi32) return -1;
+
+ /* trailer */
+ p->b->used = bz.total_out_lo32;
+
+ if (BZ_OK != BZ2_bzCompressEnd(&bz)) {
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+static int deflate_file_to_file(server *srv, connection *con, plugin_data *p, buffer *fn, file_cache_entry *fce, int type) {
+ int ifd, ofd;
+ int ret = -1;
+ void *start;
+ const char *filename = fn->ptr;
+ ssize_t r;
+
+ /* overflow */
+ if ((off_t)(fce->st.st_size * 1.1) < fce->st.st_size) return -1;
+
+ /* don't mmap files > size_t
+ *
+ * we could use a sliding window, but currently there is no need for it
+ */
+
+ if (fce->st.st_size > SIZE_MAX) return -1;
+
+ buffer_reset(p->ofn);
+ buffer_copy_string_buffer(p->ofn, p->conf.compress_cache_dir);
+ BUFFER_APPEND_SLASH(p->ofn);
+
+ if (0 == strncmp(con->physical.path->ptr, con->physical.doc_root->ptr, con->physical.doc_root->used-1)) {
+ size_t offset = p->ofn->used - 1;
+ char *dir, *nextdir;
+
+ buffer_append_string(p->ofn, con->physical.path->ptr + con->physical.doc_root->used - 1);
+
+ buffer_copy_string_buffer(p->b, p->ofn);
+
+ /* mkdir -p ... */
+ for (dir = p->b->ptr + offset; NULL != (nextdir = strchr(dir, '/')); dir = nextdir + 1) {
+ *nextdir = '\0';
+
+ if (-1 == mkdir(p->b->ptr, 0700)) {
+ if (errno != EEXIST) {
+ log_error_write(srv, __FILE__, __LINE__, "ssss", "creating cache-directory", p->b->ptr, "failed", strerror(errno));
+
+ return -1;
+ }
+ }
+
+ *nextdir = '/';
+ }
+ } else {
+ buffer_append_string_buffer(p->ofn, con->uri.path);
+ }
+
+ switch(type) {
+ case HTTP_ACCEPT_ENCODING_GZIP:
+ buffer_append_string(p->ofn, "-gzip-");
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ buffer_append_string(p->ofn, "-deflate-");
+ break;
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ buffer_append_string(p->ofn, "-bzip2-");
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unknown compression type", type);
+ return -1;
+ }
+
+ buffer_append_string_buffer(p->ofn, fce->etag);
+
+ if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL, 0600))) {
+ if (errno == EEXIST) {
+ /* cache-entry exists */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache hit");
+#endif
+ buffer_copy_string_buffer(con->physical.path, p->ofn);
+
+ return 0;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "creating cachefile", p->ofn, "failed", strerror(errno));
+
+ return -1;
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache miss");
+#endif
+ if (-1 == (ifd = open(filename, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
+
+ close(ofd);
+
+ return -1;
+ }
+
+
+ if (MAP_FAILED == (start = mmap(NULL, fce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno));
+
+ close(ofd);
+ close(ifd);
+ return -1;
+ }
+
+ switch(type) {
+#ifdef USE_ZLIB
+ case HTTP_ACCEPT_ENCODING_GZIP:
+ ret = deflate_file_to_buffer_gzip(srv, con, p, start, fce->st.st_size, fce->st.st_mtime);
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ ret = deflate_file_to_buffer_deflate(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+#ifdef USE_BZ2LIB
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ ret = deflate_file_to_buffer_bzip2(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (-1 == (r = write(ofd, p->b->ptr, p->b->used))) {
+ return -1;
+ }
+
+ if ((size_t)r != p->b->used) {
+
+ }
+
+ munmap(start, fce->st.st_size);
+ close(ofd);
+ close(ifd);
+
+ if (ret != 0) return -1;
+
+ buffer_copy_string_buffer(con->physical.path, p->ofn);
+
+ return 0;
+}
+
+static int deflate_file_to_buffer(server *srv, connection *con, plugin_data *p, buffer *fn, file_cache_entry *fce, int type) {
+ int ifd;
+ int ret = -1;
+ void *start;
+ buffer *b;
+
+ /* overflow */
+ if ((off_t)(fce->st.st_size * 1.1) < fce->st.st_size) return -1;
+
+ /* don't mmap files > size_t
+ *
+ * we could use a sliding window, but currently there is no need for it
+ */
+
+ if (fce->st.st_size > SIZE_MAX) return -1;
+
+
+ if (-1 == (ifd = open(fn->ptr, O_RDONLY))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno));
+
+ return -1;
+ }
+
+
+ if (MAP_FAILED == (start = mmap(NULL, fce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno));
+
+ close(ifd);
+ return -1;
+ }
+
+ switch(type) {
+#ifdef USE_ZLIB
+ case HTTP_ACCEPT_ENCODING_GZIP:
+ ret = deflate_file_to_buffer_gzip(srv, con, p, start, fce->st.st_size, fce->st.st_mtime);
+ break;
+ case HTTP_ACCEPT_ENCODING_DEFLATE:
+ ret = deflate_file_to_buffer_deflate(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+#ifdef USE_BZ2LIB
+ case HTTP_ACCEPT_ENCODING_BZIP2:
+ ret = deflate_file_to_buffer_bzip2(srv, con, p, start, fce->st.st_size);
+ break;
+#endif
+ default:
+ ret = -1;
+ break;
+ }
+
+ munmap(start, fce->st.st_size);
+ close(ifd);
+
+ if (ret != 0) return -1;
+
+ chunkqueue_reset(con->write_queue);
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_memory(b, p->b->ptr, p->b->used);
+
+ buffer_reset(con->physical.path);
+
+ con->file_finished = 1;
+ con->file_started = 1;
+
+ return 0;
+}
+
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_compress_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.cache-dir"))) {
+ PATCH(compress_cache_dir);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.filetype"))) {
+ PATCH(compress);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.max-filesize"))) {
+ PATCH(compress_max_filesize);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_compress_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(compress_cache_dir);
+ PATCH(compress);
+ PATCH(compress_max_filesize);
+
+ return 0;
+}
+#undef PATCH
+
+PHYSICALPATH_FUNC(mod_compress_physical) {
+ plugin_data *p = p_d;
+ data_string *content_ds;
+ size_t m, i;
+ off_t max_fsize;
+
+ /* only GET and POST can get compressed */
+ if (con->request.http_method != HTTP_METHOD_GET &&
+ con->request.http_method != HTTP_METHOD_POST) {
+ return HANDLER_GO_ON;
+ }
+
+ mod_compress_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_compress_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+
+ max_fsize = p->conf.compress_max_filesize;
+
+ /* don't compress files that are too large as we need to much time to handle them */
+ if (max_fsize && (con->fce->st.st_size >> 10) > max_fsize) return HANDLER_GO_ON;
+
+ if (NULL == (content_ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbb", "Content-Type is not set for", con->physical.path, con->uri.path);
+
+ return HANDLER_GO_ON;
+ }
+
+ /* check if mimetype is in compress-config */
+ for (m = 0; m < p->conf.compress->used; m++) {
+ data_string *compress_ds = (data_string *)p->conf.compress->data[m];
+
+ if (!compress_ds) {
+ log_error_write(srv, __FILE__, __LINE__, "sbb", "evil", con->physical.path, con->uri.path);
+
+ return HANDLER_GO_ON;
+ }
+
+ if (buffer_is_equal(compress_ds->value, content_ds->value)) {
+ /* mimetype found */
+ data_string *ds;
+
+ /* the response might change according to Accept-Encoding */
+ response_header_insert(srv, con, CONST_STR_LEN("Vary"), CONST_STR_LEN("Accept-Encoding"));
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Accept-Encoding"))) {
+ int accept_encoding = 0;
+ char *value = ds->value->ptr;
+ int srv_encodings = 0;
+ int matched_encodings = 0;
+
+ /* get client side support encodings */
+ if (NULL != strstr(value, "gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
+ if (NULL != strstr(value, "deflate")) accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
+ if (NULL != strstr(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS;
+ if (NULL != strstr(value, "bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
+ if (NULL != strstr(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY;
+
+ /* get server side supported ones */
+#ifdef USE_BZ2LIB
+ srv_encodings |= HTTP_ACCEPT_ENCODING_BZIP2;
+#endif
+#ifdef USE_ZLIB
+ srv_encodings |= HTTP_ACCEPT_ENCODING_GZIP;
+ srv_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE;
+#endif
+
+ /* find matching entries */
+ matched_encodings = accept_encoding & srv_encodings;
+
+ if (matched_encodings) {
+ const char *dflt_gzip = "gzip";
+ const char *dflt_deflate = "deflate";
+ const char *dflt_bzip2 = "bzip2";
+
+ const char *compression_name = NULL;
+ int compression_type = 0;
+
+ /* select best matching encoding */
+ if (matched_encodings & HTTP_ACCEPT_ENCODING_BZIP2) {
+ compression_type = HTTP_ACCEPT_ENCODING_BZIP2;
+ compression_name = dflt_bzip2;
+ } else if (matched_encodings & HTTP_ACCEPT_ENCODING_GZIP) {
+ compression_type = HTTP_ACCEPT_ENCODING_GZIP;
+ compression_name = dflt_gzip;
+ } else if (matched_encodings & HTTP_ACCEPT_ENCODING_DEFLATE) {
+ compression_type = HTTP_ACCEPT_ENCODING_DEFLATE;
+ compression_name = dflt_deflate;
+ }
+
+ /* deflate it */
+ if (p->conf.compress_cache_dir->used) {
+ if (0 == deflate_file_to_file(srv, con, p,
+ con->physical.path, con->fce, compression_type)) {
+ struct tm *tm;
+ time_t last_mod;
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
+
+ /* Set Last-Modified of ORIGINAL file */
+ last_mod = con->fce->st.st_mtime;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ if (srv->mtime_cache[i].mtime == last_mod) break;
+
+ if (srv->mtime_cache[i].mtime == 0) {
+ srv->mtime_cache[i].mtime = last_mod;
+
+ buffer_prepare_copy(srv->mtime_cache[i].str, 1024);
+
+ tm = gmtime(&(srv->mtime_cache[i].mtime));
+ srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr,
+ srv->mtime_cache[i].str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ srv->mtime_cache[i].str->used++;
+ break;
+ }
+ }
+
+ if (i == FILE_CACHE_MAX) {
+ i = 0;
+
+ srv->mtime_cache[i].mtime = last_mod;
+ buffer_prepare_copy(srv->mtime_cache[i].str, 1024);
+ tm = gmtime(&(srv->mtime_cache[i].mtime));
+ srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr,
+ srv->mtime_cache[i].str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", tm);
+ srv->mtime_cache[i].str->used++;
+ }
+
+ response_header_insert(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(srv->mtime_cache[i].str));
+
+ return HANDLER_FINISHED;
+ }
+ } else if (0 == deflate_file_to_buffer(srv, con, p,
+ con->physical.path, con->fce, compression_type)) {
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name));
+
+ return HANDLER_FINISHED;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+int mod_compress_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("compress");
+
+ p->init = mod_compress_init;
+ p->set_defaults = mod_compress_setdefaults;
+ p->handle_physical_path = mod_compress_physical;
+ p->cleanup = mod_compress_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_evhost.c b/src/mod_evhost.c
new file mode 100644
index 00000000..4a07a923
--- /dev/null
+++ b/src/mod_evhost.c
@@ -0,0 +1,340 @@
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "response.h"
+#include "file_cache.h"
+
+typedef struct {
+ /* unparsed pieces */
+ buffer *path_pieces_raw;
+
+ /* pieces for path creation */
+ size_t len;
+ buffer **path_pieces;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *tmp_buf;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_evhost_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_evhost_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if(s->path_pieces) {
+ for (i = 0; i < s->len; i++) {
+ buffer_free(s->path_pieces[i]);
+ }
+
+ free(s->path_pieces);
+ }
+
+ buffer_free(s->path_pieces_raw);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static void mod_evhost_parse_pattern(plugin_config *s) {
+ char *ptr = s->path_pieces_raw->ptr,*pos;
+
+ s->path_pieces = NULL;
+
+ for(pos=ptr;*ptr;ptr++) {
+ if(*ptr == '%') {
+ s->path_pieces = realloc(s->path_pieces,(s->len+2) * sizeof(*s->path_pieces));
+ s->path_pieces[s->len] = buffer_init();
+ s->path_pieces[s->len+1] = buffer_init();
+
+ buffer_copy_string_len(s->path_pieces[s->len],pos,ptr-pos);
+ pos = ptr + 2;
+
+ buffer_copy_string_len(s->path_pieces[s->len+1],ptr++,2);
+
+ s->len += 2;
+ }
+ }
+
+ if(*pos != '\0') {
+ s->path_pieces = realloc(s->path_pieces,(s->len+1) * sizeof(*s->path_pieces));
+ s->path_pieces[s->len] = buffer_init();
+
+ buffer_append_memory(s->path_pieces[s->len],pos,ptr-pos);
+
+ s->len += 1;
+ }
+}
+
+SETDEFAULTS_FUNC(mod_evhost_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ /**
+ *
+ * #
+ * # define a pattern for the host url finding
+ * # %% => % sign
+ * # %0 => domain name + tld
+ * # %1 => tld
+ * # %2 => domain name without tld
+ * # %3 => subdomain 1 name
+ * # %4 => subdomain 2 name
+ * #
+ * evhost.path-pattern = "/home/ckruse/dev/www/%3/htdocs/"
+ *
+ */
+
+ config_values_t cv[] = {
+ { "evhost.path-pattern", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ s->path_pieces_raw = buffer_init();
+ s->path_pieces = NULL;
+ s->len = 0;
+
+ cv[0].destination = s->path_pieces_raw;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (s->path_pieces_raw->used != 0) {
+ mod_evhost_parse_pattern(s);
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ * assign the different parts of the domain to array-indezes
+ * - %0 - full hostname (authority w/o port)
+ * - %1 - tld
+ * - %2 - domain.tld
+ * - %3 -
+ */
+
+static int mod_evhost_parse_host(connection *con,array *host) {
+ /* con->uri.authority->used is always > 0 if we come here */
+ register char *ptr = con->uri.authority->ptr + con->uri.authority->used - 1;
+ char *colon = ptr; /* needed to filter out the colon (if exists) */
+ int first = 1;
+ data_string *ds;
+ int i;
+
+ /* first, find the domain + tld */
+ for(;ptr > con->uri.authority->ptr;ptr--) {
+ if(*ptr == '.') {
+ if(first) first = 0;
+ else break;
+ } else if(*ptr == ':') {
+ colon = ptr;
+ first = 1;
+ }
+ }
+
+ ds = data_string_init();
+ buffer_copy_string(ds->key,"%0");
+
+ /* if we stopped at a dot, skip the dot */
+ if (*ptr == '.') ptr++;
+ buffer_copy_string_len(ds->value, ptr, colon-ptr);
+
+ array_insert_unique(host,(data_unset *)ds);
+
+ /* if the : is not the start of the authority, go on parsing the hostname */
+
+ if (colon != con->uri.authority->ptr) {
+ for(ptr = colon - 1, i = 1; ptr > con->uri.authority->ptr; ptr--) {
+ if(*ptr == '.') {
+ if (ptr != colon - 1) {
+ /* is something between the dots */
+ ds = data_string_init();
+ buffer_copy_string(ds->key,"%");
+ buffer_append_long(ds->key, i++);
+ buffer_copy_string_len(ds->value,ptr+1,colon-ptr-1);
+
+ array_insert_unique(host,(data_unset *)ds);
+ }
+ colon = ptr;
+ }
+ }
+
+ /* if the . is not the first charactor of the hostname */
+ if (colon != ptr) {
+ ds = data_string_init();
+ buffer_copy_string(ds->key,"%");
+ buffer_append_long(ds->key, i++);
+ buffer_copy_string_len(ds->value,ptr,colon-ptr);
+
+ array_insert_unique(host,(data_unset *)ds);
+ }
+ }
+
+ return 0;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_evhost_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("evhost.path-pattern"))) {
+ PATCH(path_pieces);
+ PATCH(len);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_evhost_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(path_pieces);
+ PATCH(len);
+
+ return 0;
+}
+#undef PATCH
+
+
+static handler_t mod_evhost_uri_handler(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ size_t i;
+ array *parsed_host;
+ register char *ptr;
+ int not_good = 0;
+
+ /* not authority set */
+ if (con->uri.authority->used == 0) return HANDLER_GO_ON;
+
+ mod_evhost_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_evhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ parsed_host = array_init();
+
+ mod_evhost_parse_host(con, parsed_host);
+
+ /* build document-root */
+ buffer_reset(p->tmp_buf);
+
+ for (i = 0; i < p->conf.len; i++) {
+ ptr = p->conf.path_pieces[i]->ptr;
+ if (*ptr == '%') {
+ data_string *ds;
+
+ if (*(ptr+1) == '%') {
+ /* %% */
+ BUFFER_APPEND_STRING_CONST(p->tmp_buf,"%");
+ } else if (NULL != (ds = (data_string *)array_get_element(parsed_host,p->conf.path_pieces[i]->ptr))) {
+ if (ds->value->used) {
+ buffer_append_string_buffer(p->tmp_buf,ds->value);
+ }
+ } else {
+ /* unhandled %-sequence */
+ }
+ } else {
+ buffer_append_string_buffer(p->tmp_buf,p->conf.path_pieces[i]);
+ }
+ }
+
+ BUFFER_APPEND_SLASH(p->tmp_buf);
+
+ array_free(parsed_host);
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, p->tmp_buf, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
+ not_good = 1;
+ } else if(!S_ISDIR(con->fce->st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "not a directory:", p->tmp_buf);
+ not_good = 1;
+ }
+
+ if (!not_good) {
+ buffer_copy_string_buffer(con->physical.doc_root, p->tmp_buf);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+int mod_evhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("evhost");
+ p->init = mod_evhost_init;
+ p->set_defaults = mod_evhost_set_defaults;
+ p->handle_docroot = mod_evhost_uri_handler;
+ p->cleanup = mod_evhost_free;
+
+ p->data = NULL;
+
+ return 0;
+}
+
+/* eof */
diff --git a/src/mod_expire.c b/src/mod_expire.c
new file mode 100644
index 00000000..e22e2a32
--- /dev/null
+++ b/src/mod_expire.c
@@ -0,0 +1,356 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "response.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/**
+ * this is a expire module for a lighttpd
+ *
+ * set 'Expires:' HTTP Headers on demand
+ */
+
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *expire_url;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *expire_tstmp;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_expire_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->expire_tstmp = buffer_init();
+
+ buffer_prepare_copy(p->expire_tstmp, 255);
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_expire_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->expire_tstmp);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->expire_url);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int mod_expire_get_offset(server *srv, plugin_data *p, buffer *expire, int *offset) {
+ char *ts;
+ int type = -1;
+ int retts = 0;
+
+ UNUSED(p);
+
+ /*
+ * parse
+ *
+ * '(access|modification) [plus] {<num> <type>}*'
+ *
+ * e.g. 'access 1 years'
+ */
+
+ if (expire->used == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "empty:");
+ return -1;
+ }
+
+ ts = expire->ptr;
+
+ if (0 == strncmp(ts, "access ", 7)) {
+ type = 0;
+ ts += 7;
+ } else if (0 == strncmp(ts, "modification ", 13)) {
+ type = 0;
+ ts += 13;
+ } else {
+ /* invalid type-prefix */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "invalid <base>:", ts);
+ return -1;
+ }
+
+ if (0 == strncmp(ts, "plus ", 5)) {
+ /* skip the optional plus */
+ ts += 5;
+ }
+
+ /* the rest is just <number> (years|months|days|hours|minutes|seconds) */
+ while (1) {
+ char *space, *err;
+ int num;
+
+ if (NULL == (space = strchr(ts, ' '))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "missing space after <num>:", ts);
+ return -1;
+ }
+
+ num = strtol(ts, &err, 10);
+ if (*err != ' ') {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "missing <type> after <num>:", ts);
+ return -1;
+ }
+
+ ts = space + 1;
+
+ if (NULL != (space = strchr(ts, ' '))) {
+ int slen;
+ /* */
+
+ slen = space - ts;
+
+ if (slen == 5 &&
+ 0 == strncmp(ts, "years", slen)) {
+ num *= 60 * 60 * 24 * 30 * 12;
+ } else if (slen == 6 &&
+ 0 == strncmp(ts, "months", slen)) {
+ num *= 60 * 60 * 24 * 30;
+ } else if (slen == 4 &&
+ 0 == strncmp(ts, "days", slen)) {
+ num *= 60 * 60 * 24;
+ } else if (slen == 5 &&
+ 0 == strncmp(ts, "hours", slen)) {
+ num *= 60 * 60;
+ } else if (slen == 7 &&
+ 0 == strncmp(ts, "minutes", slen)) {
+ num *= 60;
+ } else if (slen == 7 &&
+ 0 == strncmp(ts, "seconds", slen)) {
+ num *= 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unknown type:", ts);
+ return -1;
+ }
+
+ retts += num;
+
+ ts = space + 1;
+ } else {
+ if (0 == strcmp(ts, "years")) {
+ num *= 60 * 60 * 24 * 30 * 12;
+ } else if (0 == strcmp(ts, "months")) {
+ num *= 60 * 60 * 24 * 30;
+ } else if (0 == strcmp(ts, "days")) {
+ num *= 60 * 60 * 24;
+ } else if (0 == strcmp(ts, "hours")) {
+ num *= 60 * 60;
+ } else if (0 == strcmp(ts, "minutes")) {
+ num *= 60;
+ } else if (0 == strcmp(ts, "seconds")) {
+ num *= 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "unknown type:", ts);
+ return -1;
+ }
+
+ retts += num;
+
+ break;
+ }
+ }
+
+ if (offset != NULL) *offset = retts;
+
+ return type;
+}
+
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_expire_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0, k;
+
+ config_values_t cv[] = {
+ { "expire.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->expire_url = array_init();
+
+ cv[0].destination = s->expire_url;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ for (k = 0; k < s->expire_url->used; k++) {
+ data_string *ds = (data_string *)s->expire_url->data[k];
+
+ /* parse lines */
+ if (-1 == mod_expire_get_offset(srv, p, ds->value, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "parsing expire.url failed:", ds->value);
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_expire_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("expire.url"))) {
+ PATCH(expire_url);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_expire_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(expire_url);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_expire_uri_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k, i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_expire_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_expire_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ s_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.expire_url->used; k++) {
+ data_string *ds = (data_string *)p->conf.expire_url->data[k];
+ int ct_len = ds->key->used - 1;
+
+ if (ct_len > s_len) continue;
+ if (ds->key->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr, ds->key->ptr, ct_len)) {
+ int ts;
+ time_t t;
+ size_t len;
+
+ mod_expire_get_offset(srv, p, ds->value, &ts);
+ t = (ts += srv->cur_ts);
+
+ if (0 == (len = strftime(p->expire_tstmp->ptr, p->expire_tstmp->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(t))))) {
+ /* could not set expire header, out of mem */
+
+ return HANDLER_GO_ON;
+
+ }
+
+ p->expire_tstmp->used = len + 1;
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_BUF_LEN(p->expire_tstmp));
+
+ return HANDLER_GO_ON;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_expire_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("expire");
+
+ p->init = mod_expire_init;
+ p->handle_uri_clean = mod_expire_uri_handler;
+ p->set_defaults = mod_expire_set_defaults;
+ p->cleanup = mod_expire_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c
new file mode 100644
index 00000000..5438e6fa
--- /dev/null
+++ b/src/mod_fastcgi.c
@@ -0,0 +1,3209 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <signal.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "response.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+#include <fastcgi.h>
+#include <stdio.h>
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX 108
+#endif
+
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+
+/*
+ *
+ * TODO:
+ *
+ * - add timeout for a connect to a non-fastcgi process
+ * (use state_timestamp + state)
+ *
+ */
+
+typedef struct fcgi_proc {
+ size_t id; /* id will be between 1 and max_procs */
+ buffer *socket; /* config.socket + "-" + id */
+ unsigned port; /* config.port + pno */
+
+ pid_t pid; /* PID of the spawned process (0 if not spawned locally) */
+
+
+ size_t load; /* number of requests waiting on this process */
+
+ time_t last_used; /* see idle_timeout */
+ size_t requests; /* see max_requests */
+ struct fcgi_proc *prev, *next; /* see first */
+
+ time_t disable_ts; /* replace by host->something */
+
+ int is_local;
+
+ enum { PROC_STATE_UNSET, /* init-phase */
+ PROC_STATE_RUNNING, /* alive */
+ PROC_STATE_DIED_WAIT_FOR_PID,
+ PROC_STATE_KILLED, /* was killed as we don't have the load anymore */
+ PROC_STATE_DIED, /* marked as dead, should be restarted */
+ PROC_STATE_DISABLED /* proc disabled as it resulted in an error */
+ } state;
+} fcgi_proc;
+
+typedef struct {
+ /* list of processes handling this extension
+ * sorted by lowest load
+ *
+ * whenever a job is done move it up in the list
+ * until it is sorted, move it down as soon as the
+ * job is started
+ */
+ fcgi_proc *first;
+ fcgi_proc *unused_procs;
+
+ /*
+ * spawn at least min_procs, at max_procs.
+ *
+ * as soon as the load of the first entry
+ * is max_load_per_proc we spawn a new one
+ * and add it to the first entry and give it
+ * the load
+ *
+ */
+
+ unsigned short min_procs;
+ unsigned short max_procs;
+ size_t num_procs; /* how many procs are started */
+ size_t active_procs; /* how many of them are really running */
+
+ unsigned short max_load_per_proc;
+
+ /*
+ * kick the process from the list if it was not
+ * used for idle_timeout until min_procs is
+ * reached. this helps to get the processlist
+ * small again we had a small peak load.
+ *
+ */
+
+ unsigned short idle_timeout;
+
+ /*
+ * time after a disabled remote connection is tried to be re-enabled
+ *
+ *
+ */
+
+ unsigned short disable_time;
+
+ /*
+ * same fastcgi processes get a little bit larger
+ * than wanted. max_requests_per_proc kills a
+ * process after a number of handled requests.
+ *
+ */
+ size_t max_requests_per_proc;
+
+
+ /* config */
+
+ /*
+ * host:port
+ *
+ * if host is one of the local IP adresses the
+ * whole connection is local
+ *
+ * if tcp/ip should be used host AND port have
+ * to be specified
+ *
+ */
+ buffer *host;
+ unsigned short port;
+
+ /*
+ * Unix Domain Socket
+ *
+ * instead of TCP/IP we can use Unix Domain Sockets
+ * - more secure (you have fileperms to play with)
+ * - more control (on locally)
+ * - more speed (no extra overhead)
+ */
+ buffer *unixsocket;
+
+ /* if socket is local we can start the fastcgi
+ * process ourself
+ *
+ * bin-path is the path to the binary
+ *
+ * check min_procs and max_procs for the number
+ * of process to start-up
+ */
+ buffer *bin_path;
+
+ /* bin-path is set bin-environment is taken to
+ * create the environement before starting the
+ * FastCGI process
+ *
+ */
+ array *bin_env;
+
+ array *bin_env_copy;
+
+ /*
+ * docroot-translation between URL->phys and the
+ * remote host
+ *
+ * reasons:
+ * - different dir-layout if remote
+ * - chroot if local
+ *
+ */
+ buffer *docroot;
+
+ /*
+ * fastcgi-mode:
+ * - responser
+ * - authorizer
+ *
+ */
+ unsigned short mode;
+
+ /*
+ * check_local tell you if the phys file is stat()ed
+ * or not. FastCGI doesn't care if the service is
+ * remote. If the web-server side doesn't contain
+ * the fastcgi-files we should not stat() for them
+ * and say '404 not found'.
+ */
+ unsigned short check_local;
+
+
+ ssize_t load; /* replace by host->load */
+
+ size_t max_id; /* corresponds most of the time to
+ num_procs.
+
+ only if a process is killed max_id waits for the process itself
+ to die and decrements its afterwards */
+} fcgi_extension_host;
+
+/*
+ * one extension can have multiple hosts assigned
+ * one host can spawn additional processes on the same
+ * socket (if we control it)
+ *
+ * ext -> host -> procs
+ * 1:n 1:n
+ *
+ * if the fastcgi process is remote that whole goes down
+ * to
+ *
+ * ext -> host -> procs
+ * 1:n 1:1
+ *
+ * in case of PHP and FCGI_CHILDREN we have again a procs
+ * but we don't control it directly.
+ *
+ */
+
+typedef struct {
+ buffer *key; /* like .php */
+
+ fcgi_extension_host **hosts;
+
+ size_t used;
+ size_t size;
+} fcgi_extension;
+
+typedef struct {
+ fcgi_extension **exts;
+
+ size_t used;
+ size_t size;
+} fcgi_exts;
+
+
+typedef struct {
+ fcgi_exts *exts;
+
+ int debug;
+} plugin_config;
+
+typedef struct {
+ size_t *ptr;
+ size_t used;
+ size_t size;
+} buffer_uint;
+
+typedef struct {
+ char **ptr;
+
+ size_t size;
+ size_t used;
+} char_array;
+
+/* generic plugin data, shared between all connections */
+typedef struct {
+ PLUGIN_DATA;
+ buffer_uint fcgi_request_id;
+
+ buffer *fcgi_env;
+
+ buffer *path;
+ buffer *parse_response;
+
+ plugin_config **config_storage;
+
+ plugin_config conf; /* this is only used as long as no handler_ctx is setup */
+} plugin_data;
+
+/* connection specific data */
+typedef enum { FCGI_STATE_INIT, FCGI_STATE_CONNECT, FCGI_STATE_PREPARE_WRITE,
+ FCGI_STATE_WRITE, FCGI_STATE_READ
+} fcgi_connection_state_t;
+
+typedef struct {
+ buffer *response;
+ size_t response_len;
+ int response_type;
+ int response_padding;
+ size_t response_request_id;
+
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+
+ fcgi_connection_state_t state;
+ time_t state_timestamp;
+
+ int reconnects; /* number of reconnect attempts */
+
+ buffer *write_buffer;
+ size_t write_offset;
+
+ read_buffer *rb;
+
+ buffer *response_header;
+
+ int delayed; /* flag to mark that the connect() is delayed */
+
+ size_t request_id;
+ int fd; /* fd to the fastcgi process */
+ int fde_ndx; /* index into the fd-event buffer */
+
+ size_t path_info_offset; /* start of path_info in uri.path */
+
+ plugin_config conf;
+
+ connection *remote_conn; /* dumb pointer */
+ plugin_data *plugin_data; /* dumb pointer */
+} handler_ctx;
+
+
+/* ok, we need a prototype */
+static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents);
+
+int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc);
+
+
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+ assert(hctx);
+
+ hctx->fde_ndx = -1;
+
+ hctx->response = buffer_init();
+ hctx->response_header = buffer_init();
+ hctx->write_buffer = buffer_init();
+
+ hctx->request_id = 0;
+ hctx->state = FCGI_STATE_INIT;
+ hctx->proc = NULL;
+
+ hctx->response_len = 0;
+ hctx->response_type = 0;
+ hctx->response_padding = 0;
+ hctx->response_request_id = 0;
+ hctx->fd = -1;
+
+ hctx->reconnects = 0;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+ buffer_free(hctx->write_buffer);
+
+ if (hctx->rb) {
+ if (hctx->rb->ptr) free(hctx->rb->ptr);
+ free(hctx->rb);
+ }
+
+ free(hctx);
+}
+
+fcgi_proc *fastcgi_process_init() {
+ fcgi_proc *f;
+
+ f = calloc(1, sizeof(*f));
+ f->socket = buffer_init();
+
+ f->prev = NULL;
+ f->next = NULL;
+
+ return f;
+}
+
+void fastcgi_process_free(fcgi_proc *f) {
+ if (!f) return;
+
+ fastcgi_process_free(f->next);
+
+ buffer_free(f->socket);
+
+ free(f);
+}
+
+fcgi_extension_host *fastcgi_host_init() {
+ fcgi_extension_host *f;
+
+ f = calloc(1, sizeof(*f));
+
+ f->host = buffer_init();
+ f->unixsocket = buffer_init();
+ f->docroot = buffer_init();
+ f->bin_path = buffer_init();
+ f->bin_env = array_init();
+ f->bin_env_copy = array_init();
+
+ return f;
+}
+
+void fastcgi_host_free(fcgi_extension_host *h) {
+ if (!h) return;
+
+ buffer_free(h->host);
+ buffer_free(h->unixsocket);
+ buffer_free(h->docroot);
+ buffer_free(h->bin_path);
+ array_free(h->bin_env);
+ array_free(h->bin_env_copy);
+
+ fastcgi_process_free(h->first);
+ fastcgi_process_free(h->unused_procs);
+
+ free(h);
+
+}
+
+fcgi_exts *fastcgi_extensions_init() {
+ fcgi_exts *f;
+
+ f = calloc(1, sizeof(*f));
+
+ return f;
+}
+
+void fastcgi_extensions_free(fcgi_exts *f) {
+ size_t i;
+
+ if (!f) return;
+
+ for (i = 0; i < f->used; i++) {
+ fcgi_extension *fe;
+ size_t j;
+
+ fe = f->exts[i];
+
+ for (j = 0; j < fe->used; j++) {
+ fcgi_extension_host *h;
+
+ h = fe->hosts[j];
+
+ fastcgi_host_free(h);
+ }
+
+ buffer_free(fe->key);
+ free(fe->hosts);
+
+ free(fe);
+ }
+
+ free(f->exts);
+
+ free(f);
+}
+
+int fastcgi_extension_insert(fcgi_exts *ext, buffer *key, fcgi_extension_host *fh) {
+ fcgi_extension *fe;
+ size_t i;
+
+ /* there is something */
+
+ for (i = 0; i < ext->used; i++) {
+ if (buffer_is_equal(key, ext->exts[i]->key)) {
+ break;
+ }
+ }
+
+ if (i == ext->used) {
+ /* filextension is new */
+ fe = calloc(1, sizeof(*fe));
+ assert(fe);
+ fe->key = buffer_init();
+ buffer_copy_string_buffer(fe->key, key);
+
+ /* */
+
+ if (ext->size == 0) {
+ ext->size = 8;
+ ext->exts = malloc(ext->size * sizeof(*(ext->exts)));
+ assert(ext->exts);
+ } else if (ext->used == ext->size) {
+ ext->size += 8;
+ ext->exts = realloc(ext->exts, ext->size * sizeof(*(ext->exts)));
+ assert(ext->exts);
+ }
+ ext->exts[ext->used++] = fe;
+ } else {
+ fe = ext->exts[i];
+ }
+
+ if (fe->size == 0) {
+ fe->size = 4;
+ fe->hosts = malloc(fe->size * sizeof(*(fe->hosts)));
+ assert(fe->hosts);
+ } else if (fe->size == fe->used) {
+ fe->size += 4;
+ fe->hosts = realloc(fe->hosts, fe->size * sizeof(*(fe->hosts)));
+ assert(fe->hosts);
+ }
+
+ fe->hosts[fe->used++] = fh;
+
+ return 0;
+
+}
+
+INIT_FUNC(mod_fastcgi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->fcgi_env = buffer_init();
+
+ p->path = buffer_init();
+ p->parse_response = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_fastcgi_free) {
+ plugin_data *p = p_d;
+ buffer_uint *r = &(p->fcgi_request_id);
+
+ UNUSED(srv);
+
+ if (r->ptr) free(r->ptr);
+
+ buffer_free(p->fcgi_env);
+ buffer_free(p->path);
+ buffer_free(p->parse_response);
+
+ if (p->config_storage) {
+ size_t i, j, n;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ fcgi_exts *exts;
+
+ if (!s) continue;
+
+ exts = s->exts;
+
+ for (j = 0; j < exts->used; j++) {
+ fcgi_extension *ex;
+
+ ex = exts->exts[j];
+
+ for (n = 0; n < ex->used; n++) {
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+
+ host = ex->hosts[n];
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->pid != 0) kill(proc->pid, SIGTERM);
+
+ if (proc->is_local &&
+ !buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ if (proc->pid != 0) kill(proc->pid, SIGTERM);
+
+ if (proc->is_local &&
+ !buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+ }
+ }
+ }
+
+ fastcgi_extensions_free(s->exts);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+static int env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ dst = malloc(key_len + val_len + 3);
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ /* add the \0 from the value */
+ memcpy(dst + key_len + 1, val, val_len + 1);
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ env->ptr[env->used++] = dst;
+
+ return 0;
+}
+
+static int fcgi_spawn_connection(server *srv,
+ plugin_data *p,
+ fcgi_extension_host *host,
+ fcgi_proc *proc) {
+ int fcgi_fd;
+ int socket_type, status;
+ struct timeval tv = { 0, 100 * 1000 };
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un fcgi_addr_un;
+#endif
+ struct sockaddr_in fcgi_addr_in;
+ struct sockaddr *fcgi_addr;
+
+ socklen_t servlen;
+
+#ifndef HAVE_FORK
+ return -1;
+#endif
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "new proc, socket:", proc->port, proc->socket);
+ }
+
+ if (!buffer_is_empty(proc->socket)) {
+ memset(&fcgi_addr, 0, sizeof(fcgi_addr));
+
+#ifdef HAVE_SYS_UN_H
+ fcgi_addr_un.sun_family = AF_UNIX;
+ strcpy(fcgi_addr_un.sun_path, proc->socket->ptr);
+
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&fcgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = proc->socket - 1 + sizeof(fcgi_addr_un.sun_family);
+#endif
+ socket_type = AF_UNIX;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: Unix Domain sockets are not supported.");
+ return -1;
+#endif
+ } else {
+ fcgi_addr_in.sin_family = AF_INET;
+
+ if (buffer_is_empty(host->host)) {
+ fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct hostent *he;
+
+ /* set a usefull default */
+ fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+
+
+ if (NULL == (he = gethostbyname(host->host->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ssb", "gethostbyname failed: ",
+ hstrerror(h_errno), host->host);
+ return -1;
+ }
+
+ if (he->h_addrtype != AF_INET) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
+ return -1;
+ }
+
+ if (he->h_length != sizeof(struct in_addr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
+ return -1;
+ }
+
+ memcpy(&(fcgi_addr_in.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
+
+ }
+ fcgi_addr_in.sin_port = htons(proc->port);
+ servlen = sizeof(fcgi_addr_in);
+
+ socket_type = AF_INET;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
+ }
+
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "failed:", strerror(errno));
+ return -1;
+ }
+
+ if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
+ /* server is not up, spawn in */
+ pid_t child;
+ int val;
+
+ if (!buffer_is_empty(proc->socket)) {
+ unlink(proc->socket->ptr);
+ }
+
+ close(fcgi_fd);
+
+ /* reopen socket */
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socket failed:", strerror(errno));
+ return -1;
+ }
+
+ val = 1;
+ if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socketsockopt failed:", strerror(errno));
+ return -1;
+ }
+
+ /* create socket */
+ if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "bind failed:", strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(fcgi_fd, 1024)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "listen failed:", strerror(errno));
+ return -1;
+ }
+
+#ifdef HAVE_FORK
+ switch ((child = fork())) {
+ case 0: {
+ buffer *b;
+ size_t i = 0;
+ char_array env;
+
+
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+ if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
+ close(FCGI_LISTENSOCK_FILENO);
+ dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
+ close(fcgi_fd);
+ }
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* build clean environment */
+ if (host->bin_env_copy->used) {
+ for (i = 0; i < host->bin_env_copy->used; i++) {
+ data_string *ds = (data_string *)host->bin_env_copy->data[i];
+ char *ge;
+
+ if (NULL != (ge = getenv(ds->value->ptr))) {
+ env_add(&env, CONST_BUF_LEN(ds->value), ge, strlen(ge));
+ }
+ }
+ } else {
+ for (i = 0; environ[i]; i++) {
+ char *eq;
+
+ if (NULL != (eq = strchr(environ[i], '='))) {
+ env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1));
+ }
+ }
+ }
+
+ /* create environment */
+ for (i = 0; i < host->bin_env->used; i++) {
+ data_string *ds = (data_string *)host->bin_env->data[i];
+
+ env_add(&env, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ for (i = 0; i < env.used; i++) {
+ /* search for PHP_FCGI_CHILDREN */
+ if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=", sizeof("PHP_FCGI_CHILDREN=") - 1)) break;
+ }
+
+ /* not found, add a default */
+ if (i == env.used) {
+ env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"), CONST_STR_LEN("1"));
+ }
+
+ env.ptr[env.used] = NULL;
+
+ b = buffer_init();
+ buffer_copy_string(b, "exec ");
+ buffer_append_string_buffer(b, host->bin_path);
+
+ /* exec the cgi */
+ execle("/bin/sh", "sh", "-c", b->ptr, NULL, env.ptr);
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "execl failed for:", host->bin_path, strerror(errno));
+
+ exit(errno);
+
+ break;
+ }
+ case -1:
+ /* error */
+ break;
+ default:
+ /* father */
+
+ /* wait */
+ select(0, NULL, NULL, NULL, &tv);
+
+ switch (waitpid(child, &status, WNOHANG)) {
+ case 0:
+ /* child still running after timeout, good */
+ break;
+ case -1:
+ /* no PID found ? should never happen */
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pid not found:", strerror(errno));
+ return -1;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child exited (is this a FastCGI binary ?):",
+ WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+ return -1;
+ }
+
+ /* register process */
+ proc->pid = child;
+ proc->last_used = srv->cur_ts;
+ proc->is_local = 1;
+
+ break;
+ }
+#endif
+ } else {
+ proc->is_local = 0;
+ proc->pid = 0;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "(debug) socket is already used, won't spawn:",
+ proc->socket);
+ }
+ }
+
+ proc->state = PROC_STATE_RUNNING;
+ host->active_procs++;
+
+ close(fcgi_fd);
+
+ return 0;
+}
+
+
+SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+ buffer *fcgi_mode = buffer_init();
+
+ config_values_t cv[] = {
+ { "fastcgi.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "fastcgi.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = malloc(sizeof(plugin_config));
+ s->exts = fastcgi_extensions_init();
+ s->debug = 0;
+
+ cv[0].destination = s->exts;
+ cv[1].destination = &(s->debug);
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * <key> = ( ... )
+ */
+
+ if (NULL != (du = array_get_element(ca, "fastcgi.server"))) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "fastcgi.server", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+
+ /*
+ * fastcgi.server = ( "<ext>" => ( ... ),
+ * "<ext>" => ( ... ) )
+ */
+
+ for (j = 0; j < da->value->used; j++) {
+ size_t n;
+ data_array *da_ext = (data_array *)da->value->data[j];
+
+ if (da->value->data[j]->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "fastcgi.server",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * da_ext->key == name of the extension
+ */
+
+ /*
+ * fastcgi.server = ( "<ext>" =>
+ * ( "<host>" => ( ... ),
+ * "<host>" => ( ... )
+ * ),
+ * "<ext>" => ... )
+ */
+
+ for (n = 0; n < da_ext->value->used; n++) {
+ data_array *da_host = (data_array *)da_ext->value->data[n];
+
+ fcgi_extension_host *df;
+
+ config_values_t fcv[] = {
+ { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "docroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "mode", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+
+ { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
+ { "min-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
+ { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
+ { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
+
+ { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
+ { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (da_host->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "ssSBS",
+ "unexpected type for key:",
+ "fastcgi.server",
+ "[", da_host->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ df = fastcgi_host_init();
+
+ df->check_local = 1;
+ df->min_procs = 4;
+ df->max_procs = 4;
+ df->max_load_per_proc = 1;
+ df->idle_timeout = 60;
+ df->mode = FCGI_RESPONDER;
+ df->disable_time = 60;
+
+ fcv[0].destination = df->host;
+ fcv[1].destination = df->docroot;
+ fcv[2].destination = fcgi_mode;
+ fcv[3].destination = df->unixsocket;
+ fcv[4].destination = df->bin_path;
+
+ fcv[5].destination = &(df->check_local);
+ fcv[6].destination = &(df->port);
+ fcv[7].destination = &(df->min_procs);
+ fcv[8].destination = &(df->max_procs);
+ fcv[9].destination = &(df->max_load_per_proc);
+ fcv[10].destination = &(df->idle_timeout);
+ fcv[11].destination = &(df->disable_time);
+
+ fcv[12].destination = df->bin_env;
+ fcv[13].destination = df->bin_env_copy;
+
+ if (0 != config_insert_values_internal(srv, da_host->value, fcv)) {
+ return HANDLER_ERROR;
+ }
+
+ if ((!buffer_is_empty(df->host) || df->port) &&
+ !buffer_is_empty(df->unixsocket)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "either host+port or socket");
+
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(df->unixsocket)) {
+ /* unix domain socket */
+
+ if (df->unixsocket->used > UNIX_PATH_MAX - 2) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "path of the unixdomain socket is too large");
+ return HANDLER_ERROR;
+ }
+ } else {
+ /* tcp/ip */
+
+ if (buffer_is_empty(df->host) &&
+ buffer_is_empty(df->bin_path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (string):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "host");
+
+ return HANDLER_ERROR;
+ } else if (df->port == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (short):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "port");
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (!buffer_is_empty(df->bin_path)) {
+ /* a local socket + self spawning */
+ size_t pno;
+
+ if (df->min_procs > df->max_procs) df->max_procs = df->min_procs;
+ if (df->max_load_per_proc < 1) df->max_load_per_proc = 0;
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd",
+ "--- fastcgi spawning local",
+ "\n\tproc:", df->bin_path,
+ "\n\tport:", df->port,
+ "\n\tsocket", df->unixsocket,
+ "\n\tmin-procs:", df->min_procs,
+ "\n\tmax-procs:", df->max_procs);
+ }
+
+ for (pno = 0; pno < df->min_procs; pno++) {
+ fcgi_proc *proc;
+
+ proc = fastcgi_process_init();
+ proc->id = df->num_procs++;
+ df->max_id++;
+
+ if (buffer_is_empty(df->unixsocket)) {
+ proc->port = df->port + pno;
+ } else {
+ buffer_copy_string_buffer(proc->socket, df->unixsocket);
+ buffer_append_string(proc->socket, "-");
+ buffer_append_long(proc->socket, pno);
+ }
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- fastcgi spawning",
+ "\n\tport:", df->port,
+ "\n\tsocket", df->unixsocket,
+ "\n\tcurrent:", pno, "/", df->min_procs);
+ }
+
+ if (fcgi_spawn_connection(srv, p, df, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "[ERROR]: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ proc->next = df->first;
+ if (df->first) df->first->prev = proc;
+
+ df->first = proc;
+ }
+ } else {
+ fcgi_proc *fp;
+
+ fp = fastcgi_process_init();
+ fp->id = df->num_procs++;
+ df->max_id++;
+ df->active_procs++;
+ fp->state = PROC_STATE_RUNNING;
+
+ if (buffer_is_empty(df->unixsocket)) {
+ fp->port = df->port;
+ } else {
+ buffer_copy_string_buffer(fp->socket, df->unixsocket);
+ }
+
+ df->first = fp;
+
+ df->min_procs = 1;
+ df->max_procs = 1;
+ }
+
+ if (!buffer_is_empty(fcgi_mode)) {
+ if (strcmp(fcgi_mode->ptr, "responder") == 0) {
+ df->mode = FCGI_RESPONDER;
+ } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) {
+ df->mode = FCGI_AUTHORIZER;
+ if (buffer_is_empty(df->docroot)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: docroot is required for authorizer mode.");
+ return HANDLER_ERROR;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "WARNING: unknown fastcgi mode:",
+ fcgi_mode, "(ignored, mode set to responder)");
+ }
+ }
+
+ /* if extension already exists, take it */
+ fastcgi_extension_insert(s->exts, da_ext->key, df);
+ }
+ }
+ }
+ }
+
+ buffer_free(fcgi_mode);
+
+ return HANDLER_GO_ON;
+}
+
+static int fcgi_set_state(server *srv, handler_ctx *hctx, fcgi_connection_state_t state) {
+ hctx->state = state;
+ hctx->state_timestamp = srv->cur_ts;
+
+ return 0;
+}
+
+
+static size_t fcgi_requestid_new(server *srv, plugin_data *p) {
+ size_t m = 0;
+ size_t i;
+ buffer_uint *r = &(p->fcgi_request_id);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] > m) m = r->ptr[i];
+ }
+
+ if (r->size == 0) {
+ r->size = 16;
+ r->ptr = malloc(sizeof(*r->ptr) * r->size);
+ } else if (r->used == r->size) {
+ r->size += 16;
+ r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size);
+ }
+
+ r->ptr[r->used++] = ++m;
+
+ return m;
+}
+
+static int fcgi_requestid_del(server *srv, plugin_data *p, size_t request_id) {
+ size_t i;
+ buffer_uint *r = &(p->fcgi_request_id);
+
+ UNUSED(srv);
+
+ for (i = 0; i < r->used; i++) {
+ if (r->ptr[i] == request_id) break;
+ }
+
+ if (i != r->used) {
+ /* found */
+
+ if (i != r->used - 1) {
+ r->ptr[i] = r->ptr[r->used - 1];
+ }
+ r->used--;
+ }
+
+ return 0;
+}
+
+void fcgi_connection_cleanup(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) {
+ WP();
+ return;
+ }
+
+ if (hctx->fd != -1) {
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ if (hctx->request_id != 0) {
+ fcgi_requestid_del(srv, p, hctx->request_id);
+ }
+
+ if (hctx->host && hctx->proc) {
+ hctx->host->load--;
+ if (hctx->state != FCGI_STATE_INIT &&
+ hctx->state != FCGI_STATE_CONNECT) {
+ /* after the connect the process gets a load */
+ hctx->proc->load--;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddb",
+ "release proc:",
+ hctx->fd,
+ hctx->proc->pid, hctx->proc->socket);
+ }
+ }
+
+ fcgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+ }
+
+
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+}
+
+static int fcgi_reconnect(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+
+ /* child died
+ *
+ * 1.
+ *
+ * connect was ok, connection was accepted
+ * but the php accept loop checks after the accept if it should die or not.
+ *
+ * if yes we can only detect it at a write()
+ *
+ * next step is resetting this attemp and setup a connection again
+ *
+ * if we have more then 5 reconnects for the same request, die
+ *
+ * 2.
+ *
+ * we have a connection but the child died by some other reason
+ *
+ */
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ close(hctx->fd);
+ srv->cur_fds--;
+
+ fcgi_requestid_del(srv, p, hctx->request_id);
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_INIT);
+
+ hctx->request_id = 0;
+ hctx->reconnects++;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddb",
+ "release proc:",
+ hctx->fd,
+ hctx->proc->pid, hctx->proc->socket);
+ }
+
+ hctx->proc->load--;
+ fcgi_proclist_sort_down(srv, hctx->host, hctx->proc);
+
+ return 0;
+}
+
+
+static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ fcgi_connection_cleanup(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+
+static int fcgi_env_add(buffer *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ size_t len;
+
+ if (!key || !val) return -1;
+
+ len = key_len + val_len;
+
+ len += key_len > 127 ? 4 : 1;
+ len += val_len > 127 ? 4 : 1;
+
+ buffer_prepare_append(env, len);
+
+ if (key_len > 127) {
+ env->ptr[env->used++] = ((key_len >> 24) & 0xff) | 0x80;
+ env->ptr[env->used++] = (key_len >> 16) & 0xff;
+ env->ptr[env->used++] = (key_len >> 8) & 0xff;
+ env->ptr[env->used++] = (key_len >> 0) & 0xff;
+ } else {
+ env->ptr[env->used++] = (key_len >> 0) & 0xff;
+ }
+
+ if (val_len > 127) {
+ env->ptr[env->used++] = ((val_len >> 24) & 0xff) | 0x80;
+ env->ptr[env->used++] = (val_len >> 16) & 0xff;
+ env->ptr[env->used++] = (val_len >> 8) & 0xff;
+ env->ptr[env->used++] = (val_len >> 0) & 0xff;
+ } else {
+ env->ptr[env->used++] = (val_len >> 0) & 0xff;
+ }
+
+ memcpy(env->ptr + env->used, key, key_len);
+ env->used += key_len;
+ memcpy(env->ptr + env->used, val, val_len);
+ env->used += val_len;
+
+ return 0;
+}
+
+static int fcgi_header(FCGI_Header * header, unsigned char type, size_t request_id, int contentLength, unsigned char paddingLength) {
+ header->version = FCGI_VERSION_1;
+ header->type = type;
+ header->requestIdB0 = request_id & 0xff;
+ header->requestIdB1 = (request_id >> 8) & 0xff;
+ header->contentLengthB0 = contentLength & 0xff;
+ header->contentLengthB1 = (contentLength >> 8) & 0xff;
+ header->paddingLength = paddingLength;
+ header->reserved = 0;
+
+ return 0;
+}
+/**
+ *
+ * returns
+ * -1 error
+ * 0 connected
+ * 1 not connected yet
+ */
+
+static int fcgi_establish_connection(server *srv, handler_ctx *hctx) {
+ struct sockaddr *fcgi_addr;
+ struct sockaddr_in fcgi_addr_in;
+#ifdef HAVE_SYS_UN_H
+ struct sockaddr_un fcgi_addr_un;
+#endif
+ socklen_t servlen;
+
+ fcgi_extension_host *host = hctx->host;
+ fcgi_proc *proc = hctx->proc;
+ int fcgi_fd = hctx->fd;
+
+ memset(&fcgi_addr, 0, sizeof(fcgi_addr));
+
+ if (!buffer_is_empty(proc->socket)) {
+#ifdef HAVE_SYS_UN_H
+ /* use the unix domain socket */
+ fcgi_addr_un.sun_family = AF_UNIX;
+ strcpy(fcgi_addr_un.sun_path, proc->socket->ptr);
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&fcgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = proc->socket->used - 1 + sizeof(fcgi_addr_un.sun_family);
+#endif
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
+#else
+ return -1;
+#endif
+ } else {
+ fcgi_addr_in.sin_family = AF_INET;
+ if (INADDR_NONE == (fcgi_addr_in.sin_addr.s_addr = inet_addr(host->host->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "converting IP-adress failed for", host->host,
+ "\nBe sure to specify an IP address here");
+
+ return -1;
+ }
+ fcgi_addr_in.sin_port = htons(proc->port);
+ servlen = sizeof(fcgi_addr_in);
+
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
+ }
+
+ if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
+ if (errno == EINPROGRESS ||
+ errno == EALREADY ||
+ errno == EINTR) {
+ if (hctx->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect delayed:", fcgi_fd);
+ }
+
+ return 1;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sdsddb",
+ "connect failed:", fcgi_fd,
+ strerror(errno), errno,
+ proc->port, proc->socket);
+
+ if (errno == EAGAIN) {
+ /* this is Linux only */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If this happend on Linux: You have been run out of local ports. "
+ "Check the manual, section Performance how to handle this.");
+ }
+
+ return -1;
+ }
+ }
+ if (hctx->conf.debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", fcgi_fd);
+ }
+
+
+
+ return 0;
+}
+
+static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
+ size_t i;
+
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ BUFFER_COPY_STRING_CONST(srv->tmp_buf, "HTTP_");
+ srv->tmp_buf->used--;
+ }
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] =
+ light_isalpha(ds->key->ptr[j]) ?
+ ds->key->ptr[j] & ~32 : '_';
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0';
+
+ fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ for (i = 0; i < con->environment->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->environment->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] =
+ isalpha((unsigned char)ds->key->ptr[j]) ?
+ toupper((unsigned char)ds->key->ptr[j]) : '_';
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0';
+
+ fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value));
+ }
+ }
+
+ return 0;
+}
+
+
+static int fcgi_create_env(server *srv, handler_ctx *hctx, size_t request_id) {
+ FCGI_BeginRequestRecord beginRecord;
+ FCGI_Header header;
+
+ char buf[32];
+ size_t offset;
+ const char *s;
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+
+ plugin_data *p = hctx->plugin_data;
+ fcgi_extension_host *host= hctx->host;
+
+ connection *con = hctx->remote_conn;
+ server_socket *srv_sock = con->srv_socket;
+
+ /* send FCGI_BEGIN_REQUEST */
+
+ fcgi_header(&(beginRecord.header), FCGI_BEGIN_REQUEST, request_id, sizeof(beginRecord.body), 0);
+ beginRecord.body.roleB0 = host->mode;
+ beginRecord.body.roleB1 = 0;
+ beginRecord.body.flags = 0;
+ memset(beginRecord.body.reserved, 0, sizeof(beginRecord.body.reserved));
+
+ buffer_copy_memory(hctx->write_buffer, (const char *)&beginRecord, sizeof(beginRecord));
+
+ /* send FCGI_PARAMS */
+ buffer_prepare_copy(p->fcgi_env, 1024);
+
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));
+
+ if (con->server_name->used) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name));
+ } else {
+#ifdef HAVE_IPV6
+ s = inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1);
+#else
+ s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
+#endif
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
+ }
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));
+
+ s = inet_ntop_cache_get_ip(srv, &(con->dst_addr));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));
+
+ if (!buffer_is_empty(con->authed_user)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_USER"),
+ CONST_BUF_LEN(con->authed_user));
+ }
+
+ if (con->request.content_length > 0 && host->mode != FCGI_AUTHORIZER) {
+ /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
+ }
+
+ if (host->mode != FCGI_AUTHORIZER) {
+ /*
+ * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
+ * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
+ * (6.1.14, 6.1.6, 6.1.7)
+ * For AUTHORIZER mode these headers should be omitted.
+ */
+
+ if (hctx->path_info_offset == 0) { /* no pathinfo */
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_STR_LEN(""));
+ } else { /* pathinfo */
+ *(con->uri.path->ptr + hctx->path_info_offset) = '\0'; /* get sctipt_name part */
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
+
+ *(con->uri.path->ptr + hctx->path_info_offset) = '/'; /* restore uri.path */
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"),
+ con->uri.path->ptr + hctx->path_info_offset,
+ con->uri.path->used - 1 - hctx->path_info_offset);
+
+ if (host->docroot->used) {
+ buffer_copy_string_buffer(p->path, host->docroot);
+ buffer_append_string(p->path, con->uri.path->ptr + hctx->path_info_offset);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path));
+ } else {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_TRANSLATED"),
+ con->uri.path->ptr + hctx->path_info_offset,
+ con->uri.path->used - 1 - hctx->path_info_offset);
+ }
+ }
+ }
+
+ /*
+ * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
+ * http://www.php.net/manual/en/reserved.variables.php
+ * treatment of PATH_TRANSLATED is different from the one of CGI specs.
+ * TODO: this code should be checked against cgi.fix_pathinfo php
+ * parameter.
+ */
+
+ if (!buffer_is_empty(host->docroot)) {
+ /*
+ * rewrite SCRIPT_FILENAME
+ *
+ */
+
+ buffer_copy_string_buffer(p->path, host->docroot);
+ buffer_append_string_buffer(p->path, con->uri.path);
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot));
+ } else {
+ if (con->request.pathinfo->used) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
+ }
+
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
+ }
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
+ if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri));
+ }
+ if (!buffer_is_empty(con->uri.query)) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
+ } else {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN(""));
+ }
+
+ s = get_http_method_name(con->request.http_method);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */
+ s = get_http_version_name(con->request.http_version);
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));
+
+#ifdef USE_OPENSSL
+ if (srv_sock->is_ssl) {
+ fcgi_env_add(p->fcgi_env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on"));
+ }
+#endif
+
+
+ fcgi_env_add_request_headers(srv, con, p);
+
+ fcgi_header(&(header), FCGI_PARAMS, request_id, p->fcgi_env->used, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header));
+ buffer_append_memory(hctx->write_buffer, (const char *)p->fcgi_env->ptr, p->fcgi_env->used);
+
+ fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header));
+
+ /* send FCGI_STDIN */
+
+ /* something to send ? */
+ for (offset = 0; offset != con->request.content_length; ) {
+ /* send chunks of 1024 bytes */
+ size_t toWrite = con->request.content_length - offset > 4096 ? 4096 : con->request.content_length - offset;
+
+ fcgi_header(&(header), FCGI_STDIN, request_id, toWrite, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header));
+ buffer_append_memory(hctx->write_buffer, (const char *)(con->request.content->ptr + offset), toWrite);
+
+ offset += toWrite;
+ }
+
+ /* terminate STDIN */
+ fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0);
+ buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header));
+
+#if 0
+ for (i = 0; i < hctx->write_buffer->used; i++) {
+ fprintf(stderr, "%02x ", hctx->write_buffer->ptr[i]);
+ if ((i+1) % 16 == 0) {
+ size_t j;
+ for (j = i-15; j <= i; j++) {
+ fprintf(stderr, "%c",
+ isprint((unsigned char)hctx->write_buffer->ptr[j]) ? hctx->write_buffer->ptr[j] : '.');
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+#endif
+
+ return 0;
+}
+
+static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
+ char *s, *ns;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ fcgi_extension_host *host= hctx->host;
+
+ UNUSED(srv);
+
+ /* \r\n -> \0\0 */
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ for (s = p->parse_response->ptr; NULL != (ns = strstr(s, "\r\n")); s = ns + 2) {
+ char *key, *value;
+ int key_len;
+ data_string *ds;
+
+ ns[0] = '\0';
+ ns[1] = '\0';
+
+ key = s;
+ if (NULL == (value = strchr(s, ':'))) {
+ /* we expect: "<key>: <value>\n" */
+ continue;
+ }
+
+ key_len = value - key;
+
+ value++;
+ /* strip WS */
+ while (*value == ' ' || *value == '\t') value++;
+
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* authorizers shouldn't affect the response headers sent back to the client */
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+ }
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 6:
+ if (0 == strncasecmp(key, "Status", key_len)) {
+ con->http_status = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_STATUS;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
+ con->parsed_response |= HTTP_CONNECTION;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+
+ if (con->response.content_length < 0) con->response.content_length = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* CGI/1.1 rev 03 - 7.2.1.2 */
+ if ((con->parsed_response & HTTP_LOCATION) &&
+ !(con->parsed_response & HTTP_STATUS)) {
+ con->http_status = 302;
+ }
+
+ return 0;
+}
+
+
+static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
+ ssize_t len;
+ int fin = 0;
+ int b;
+ ssize_t r;
+
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+ int fcgi_fd = hctx->fd;
+ fcgi_extension_host *host= hctx->host;
+ fcgi_proc *proc = hctx->proc;
+
+ /*
+ * check how much we have to read
+ */
+ if (ioctl(hctx->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ fcgi_fd);
+ return -1;
+ }
+
+ /* init read-buffer */
+ if (hctx->rb == NULL) {
+ hctx->rb = calloc(1, sizeof(*hctx->rb));
+ }
+
+ if (b > 0) {
+ if (hctx->rb->size == 0) {
+ hctx->rb->size = b;
+ hctx->rb->ptr = malloc(hctx->rb->size * sizeof(*hctx->rb->ptr));
+ } else if (hctx->rb->size < hctx->rb->used + b) {
+ hctx->rb->size += b;
+ hctx->rb->ptr = realloc(hctx->rb->ptr, hctx->rb->size * sizeof(*hctx->rb->ptr));
+ }
+
+ /* append to read-buffer */
+ if (-1 == (r = read(hctx->fd, hctx->rb->ptr + hctx->rb->used, b))) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ fcgi_fd, strerror(errno));
+ return -1;
+ }
+
+ /* this should be catched by the b > 0 above */
+ assert(r);
+
+ hctx->rb->used += r;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsdsd",
+ "unexpected end-of-file (perhaps the fastcgi process died):",
+ "pid:", proc->pid,
+ "fcgi-fd:", fcgi_fd,
+ "remote-fd:", con->fd);
+
+ return -1;
+ }
+
+ /* parse all fcgi packets
+ *
+ * start: hctx->rb->ptr
+ * end : hctx->rb->ptr + hctx->rb->used
+ *
+ */
+ while (fin == 0) {
+ size_t request_id;
+
+ if (hctx->response_len == 0) {
+ FCGI_Header *header;
+
+ if (hctx->rb->used - hctx->rb->offset < sizeof(*header)) {
+ /* didn't get the full header packet (most often 0),
+ * but didn't recieved the final packet either
+ *
+ * we will come back later and finish everything
+ *
+ */
+
+ hctx->delayed = 1;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "didn't get the full header: ",
+ hctx->rb->used - hctx->rb->offset, sizeof(*header),
+ fcgi_fd
+ );
+#endif
+ break;
+ }
+#if 0
+ fprintf(stderr, "fcgi-version: %02x\n", hctx->rb->ptr[hctx->rb->offset]);
+#endif
+
+ header = (FCGI_Header *)(hctx->rb->ptr + hctx->rb->offset);
+ hctx->rb->offset += sizeof(*header);
+
+ len = (header->contentLengthB0 | (header->contentLengthB1 << 8)) + header->paddingLength;
+ request_id = (header->requestIdB0 | (header->requestIdB1 << 8));
+
+ hctx->response_len = len;
+ hctx->response_request_id = request_id;
+ hctx->response_type = header->type;
+ hctx->response_padding = header->paddingLength;
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sddd", "offset: ",
+ fcgi_fd, hctx->rb->offset, header->type
+ );
+#endif
+
+ } else {
+ len = hctx->response_len;
+ }
+
+ if (hctx->rb->used - hctx->rb->offset < hctx->response_len) {
+ /* we are not finished yet */
+ break;
+ }
+
+ hctx->response->ptr = hctx->rb->ptr + hctx->rb->offset;
+ hctx->rb->offset += hctx->response_len;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd", "offset: ",
+ fcgi_fd, hctx->rb->offset
+ );
+#endif
+
+ /* remove padding */
+#if 0
+ hctx->response->ptr[hctx->response_len - hctx->response_padding] = '\0';
+#endif
+ hctx->response->used = hctx->response_len - hctx->response_padding + 1;
+
+ /* mark the fast-cgi packet as finished */
+ hctx->response_len = 0;
+
+ switch(hctx->response_type) {
+ case FCGI_STDOUT:
+ if (len) {
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdb", "len", len, hctx->response);
+#endif
+
+ if (0 == con->got_response) {
+ con->got_response = 1;
+ buffer_prepare_copy(hctx->response_header, 128);
+ }
+
+ if (0 == con->file_started) {
+ char *c;
+
+ /* search for the \r\n\r\n in the string */
+ if (NULL != (c = buffer_search_string_len(hctx->response, "\r\n\r\n", 4))) {
+ size_t hlen = c - hctx->response->ptr + 4;
+ size_t blen = hctx->response->used - hlen - 1;
+ /* found */
+
+ buffer_append_string_len(hctx->response_header, hctx->response->ptr, c - hctx->response->ptr + 4);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ss", "Header:", hctx->response_header->ptr);
+#endif
+ /* parse the response header */
+ fcgi_response_parse(srv, con, p, hctx->response_header);
+
+
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ con->file_started = 1;
+
+ if (blen) {
+ http_chunk_append_mem(srv, con, c + 4, blen + 1);
+ joblist_append(srv, con);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "body-len", blen);
+#endif
+ }
+ }
+ } else {
+ /* copy everything */
+ buffer_append_string_buffer(hctx->response_header, hctx->response);
+ }
+ } else {
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "body-len", hctx->response->used);
+#endif
+ }
+ } else {
+ /* finished */
+ }
+
+ break;
+ case FCGI_STDERR:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "FastCGI-stderr:", hctx->response);
+
+ break;
+ case FCGI_END_REQUEST:
+ con->file_finished = 1;
+
+ if (host->mode != FCGI_AUTHORIZER ||
+ !(con->http_status == 0 ||
+ con->http_status == 200)) {
+ /* send chunk-end if nesseary */
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+ }
+
+ fin = 1;
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "FastCGI: header.type not handled: ", hctx->response_type);
+ break;
+ }
+ }
+
+ hctx->response->ptr = NULL;
+
+ return fin;
+}
+
+int fcgi_proclist_sort_up(server *srv, fcgi_extension_host *host, fcgi_proc *proc) {
+ fcgi_proc *p;
+
+ UNUSED(srv);
+
+ /* we have been the smallest of the current list
+ * and we want to insert the node sorted as soon
+ * possible
+ *
+ * 1 0 0 0 1 1 1
+ * | ^
+ * | |
+ * +------+
+ *
+ */
+
+ /* nothing to sort, only one element */
+ if (host->first == proc && proc->next == NULL) return 0;
+
+ for (p = proc; p->next && p->next->load < proc->load; p = p->next);
+
+ /* no need to move something
+ *
+ * 1 2 2 2 3 3 3
+ * ^
+ * |
+ * +
+ *
+ */
+ if (p == proc) return 0;
+
+ if (host->first == proc) {
+ /* we have been the first elememt */
+
+ host->first = proc->next;
+ host->first->prev = NULL;
+ }
+
+ /* disconnect proc */
+
+ if (proc->prev) proc->prev->next = proc->next;
+ if (proc->next) proc->next->prev = proc->prev;
+
+ /* proc should be right of p */
+
+ proc->next = p->next;
+ proc->prev = p;
+ if (p->next) p->next->prev = proc;
+ p->next = proc;
+#if 0
+ for(p = host->first; p; p = p->next) {
+ log_error_write(srv, __FILE__, __LINE__, "dd",
+ p->pid, p->load);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ return 0;
+}
+
+int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc) {
+ fcgi_proc *p;
+
+ UNUSED(srv);
+
+ /* we have been the smallest of the current list
+ * and we want to insert the node sorted as soon
+ * possible
+ *
+ * 0 0 0 0 1 0 1
+ * ^ |
+ * | |
+ * +----------+
+ *
+ *
+ * the basic is idea is:
+ * - the last active fastcgi process should be still
+ * in ram and is not swapped out yet
+ * - processes that are not reused will be killed
+ * after some time by the trigger-handler
+ * - remember it as:
+ * everything > 0 is hot
+ * all unused procs are colder the more right they are
+ * ice-cold processes are propably unused since more
+ * than 'unused-timeout', are swaped out and won't be
+ * reused in the next seconds anyway.
+ *
+ */
+
+ /* nothing to sort, only one element */
+ if (host->first == proc && proc->next == NULL) return 0;
+
+ for (p = host->first; p != proc && p->load < proc->load; p = p->next);
+
+
+ /* no need to move something
+ *
+ * 1 2 2 2 3 3 3
+ * ^
+ * |
+ * +
+ *
+ */
+ if (p == proc) return 0;
+
+ /* we have to move left. If we are already the first element
+ * we are done */
+ if (host->first == proc) return 0;
+
+ /* release proc */
+ if (proc->prev) proc->prev->next = proc->next;
+ if (proc->next) proc->next->prev = proc->prev;
+
+ /* proc should be left of p */
+ proc->next = p;
+ proc->prev = p->prev;
+ if (p->prev) p->prev->next = proc;
+ p->prev = proc;
+
+ if (proc->prev == NULL) host->first = proc;
+#if 0
+ for(p = host->first; p; p = p->next) {
+ log_error_write(srv, __FILE__, __LINE__, "dd",
+ p->pid, p->load);
+ }
+#else
+ UNUSED(srv);
+#endif
+
+ return 0;
+}
+
+
+
+static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ fcgi_extension_host *host= hctx->host;
+ connection *con = hctx->remote_conn;
+
+ int r;
+
+ /* sanity check */
+ if (!host ||
+ ((!host->host->used || !host->port) && !host->unixsocket->used)) return HANDLER_ERROR;
+
+ switch(hctx->state) {
+ case FCGI_STATE_INIT:
+ r = host->unixsocket->used ? AF_UNIX : AF_INET;
+
+ if (-1 == (hctx->fd = socket(r, SOCK_STREAM, 0))) {
+ if (errno == EMFILE ||
+ errno == EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "wait for fd at connection:", con->fd);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdd",
+ "socket failed:", strerror(errno), srv->cur_fds, srv->max_fds);
+ return HANDLER_ERROR;
+ }
+ hctx->fde_ndx = -1;
+
+ srv->cur_fds++;
+
+ fdevent_register(srv->ev, hctx->fd, fcgi_handle_fdevent, hctx);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fcntl failed: ", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ /* fall through */
+ case FCGI_STATE_CONNECT:
+ if (hctx->state == FCGI_STATE_INIT) {
+ for (hctx->proc = hctx->host->first;
+ hctx->proc && hctx->proc->state != PROC_STATE_RUNNING;
+ hctx->proc = hctx->proc->next);
+
+ /* all childs are dead */
+ if (hctx->proc == NULL) {
+ hctx->fde_ndx = -1;
+
+ return HANDLER_ERROR;
+ }
+
+
+ switch (fcgi_establish_connection(srv, hctx)) {
+ case 1:
+ fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT);
+
+ /* connection is in progress, wait for an event and call getsockopt() below */
+
+ return HANDLER_WAIT_FOR_EVENT;
+ case -1:
+ /* if ECONNREFUSED choose another connection -> FIXME */
+ hctx->fde_ndx = -1;
+
+ return HANDLER_ERROR;
+ default:
+ /* everything is ok, go on */
+ break;
+ }
+
+
+ } else {
+ int socket_error;
+ socklen_t socket_error_len = sizeof(socket_error);
+
+ /* try to finish the connect() */
+ if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "getsockopt failed:", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+ if (socket_error != 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "establishing connection failed:", strerror(socket_error),
+ "port:", hctx->proc->port);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ /* ok, we have the connection */
+
+ hctx->proc->load++;
+ hctx->proc->last_used = srv->cur_ts;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sddbdd",
+ "got proc:",
+ hctx->fd,
+ hctx->proc->pid,
+ hctx->proc->socket,
+ hctx->proc->port,
+ hctx->proc->load);
+ }
+
+ /* move the proc-list entry down the list */
+ fcgi_proclist_sort_up(srv, hctx->host, hctx->proc);
+
+ if (hctx->request_id == 0) {
+ hctx->request_id = fcgi_requestid_new(srv, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "fcgi-request is already in use:", hctx->request_id);
+ }
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE);
+ /* fall through */
+ case FCGI_STATE_PREPARE_WRITE:
+ fcgi_create_env(srv, hctx, hctx->request_id);
+
+ fcgi_set_state(srv, hctx, FCGI_STATE_WRITE);
+ hctx->write_offset = 0;
+
+ /* fall through */
+ case FCGI_STATE_WRITE:
+ /* continue with the code after the switch */
+ if (-1 == (r = write(hctx->fd,
+ hctx->write_buffer->ptr + hctx->write_offset,
+ hctx->write_buffer->used - hctx->write_offset))) {
+
+ if (errno == ENOTCONN) {
+ /* the connection got dropped after accept()
+ *
+ * this is most of the time a PHP which dies
+ * after PHP_FCGI_MAX_REQUESTS
+ *
+ */
+ if (hctx->write_offset == 0 &&
+ hctx->reconnects < 5) {
+ usleep(10000); /* take away the load of the webserver
+ * to let the php a chance to restart
+ */
+
+ fcgi_reconnect(srv, hctx);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ /* not reconnected ... why
+ *
+ * far@#lighttpd report this for FreeBSD
+ *
+ */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "[REPORT ME] connection was dropped after accept(). reconnect() denied:",
+ "write-offset:", hctx->write_offset,
+ "reconnect attempts:", hctx->reconnects);
+
+
+
+ return HANDLER_ERROR;
+ }
+
+ if ((errno != EAGAIN) &&
+ (errno != EINTR)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), errno);
+
+ return HANDLER_ERROR;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ }
+
+ hctx->write_offset += r;
+
+ if (hctx->write_offset == hctx->write_buffer->used) {
+ fcgi_set_state(srv, hctx, FCGI_STATE_READ);
+ }
+
+ break;
+ case FCGI_STATE_READ:
+ /* waiting for a response */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_WAIT_FOR_EVENT;
+}
+
+static int fcgi_restart_dead_procs(server *srv, plugin_data *p, fcgi_extension_host *host) {
+ fcgi_proc *proc;
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdbdddd",
+ "proc:",
+ host->host, proc->port,
+ proc->socket,
+ proc->state,
+ proc->is_local,
+ proc->load,
+ proc->pid);
+ }
+
+ if (0 == proc->is_local) {
+ /*
+ * external servers might get disabled
+ *
+ * enable the server again, perhaps it is back again
+ */
+
+ if ((proc->state == PROC_STATE_DISABLED) &&
+ (srv->cur_ts - proc->disable_ts > FCGI_RETRY_TIMEOUT)) {
+ proc->state = PROC_STATE_RUNNING;
+ host->active_procs++;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdb",
+ "fcgi-server re-enabled:",
+ host->host, host->port,
+ host->unixsocket);
+ }
+ } else {
+ /* the child should not terminate at all */
+ int status;
+
+ if (proc->state == PROC_STATE_DIED_WAIT_FOR_PID) {
+ switch(waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child is still alive */
+ break;
+ case -1:
+ break;
+ default:
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "child exited, pid:", proc->pid,
+ "status:", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+
+ proc->state = PROC_STATE_DIED;
+ break;
+ }
+ }
+
+ /*
+ * local servers might died, but we restart them
+ *
+ */
+ if (proc->state == PROC_STATE_DIED &&
+ proc->load == 0) {
+ /* restart the child */
+
+ if (fcgi_spawn_connection(srv, p, host, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ fcgi_proclist_sort_down(srv, host, proc);
+ }
+ }
+ }
+
+ return 0;
+}
+
+SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) {
+ plugin_data *p = p_d;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ fcgi_proc *proc;
+ fcgi_extension_host *host;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ /* not my job */
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ /* ok, create the request */
+ switch(fcgi_write_request(srv, hctx)) {
+ case HANDLER_ERROR:
+ proc = hctx->proc;
+ host = hctx->host;
+
+ if (proc &&
+ 0 == proc->is_local &&
+ proc->state != PROC_STATE_DISABLED) {
+ /* only disable remote servers as we don't manage them*/
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdb", "fcgi-server disabled:",
+ host->host,
+ proc->port,
+ proc->socket);
+
+ /* disable this server */
+ proc->disable_ts = srv->cur_ts;
+ proc->state = PROC_STATE_DISABLED;
+ host->active_procs--;
+ }
+
+ if (hctx->state == FCGI_STATE_INIT ||
+ hctx->state == FCGI_STATE_CONNECT) {
+ /* connect() or getsockopt() failed,
+ * restart the request-handling
+ */
+ if (proc && proc->is_local) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdb", "connect() to fastcgi failed, restarting the request-handling:",
+ host->host,
+ proc->port,
+ proc->socket);
+
+ proc->state = PROC_STATE_DIED_WAIT_FOR_PID;
+ }
+
+ fcgi_restart_dead_procs(srv, p, host);
+
+ fcgi_connection_cleanup(srv, hctx);
+
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+
+ joblist_append(srv, con);
+
+ /* mis-using HANDLER_WAIT_FOR_FD to break out of the loop
+ * and hope that the childs will be restarted
+ *
+ */
+ return HANDLER_WAIT_FOR_FD;
+ } else {
+
+ fcgi_connection_cleanup(srv, hctx);
+
+ buffer_reset(con->physical.path);
+ con->mode = DIRECT;
+ con->http_status = 503;
+
+ return HANDLER_FINISHED;
+ }
+ case HANDLER_WAIT_FOR_EVENT:
+ if (con->file_started == 1) {
+ return HANDLER_FINISHED;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ default:
+ return HANDLER_ERROR;
+ }
+}
+
+static handler_t fcgi_connection_close(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "emergency exit: fastcgi:",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+
+
+ fcgi_connection_cleanup(srv, hctx);
+
+ return HANDLER_FINISHED;
+}
+
+
+static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+ plugin_data *p = hctx->plugin_data;
+
+ fcgi_proc *proc = hctx->proc;
+ fcgi_extension_host *host= hctx->host;
+
+ joblist_append(srv, con);
+
+ if ((revents & FDEVENT_IN) &&
+ hctx->state == FCGI_STATE_READ) {
+ switch (fcgi_demux_response(srv, hctx)) {
+ case 0:
+ break;
+ case 1:
+
+ if (host->mode == FCGI_AUTHORIZER &&
+ (con->http_status == 200 ||
+ con->http_status == 0)) {
+ /*
+ * If we are here in AUTHORIZER mode then a request for autorizer
+ * was proceeded already, and status 200 has been returned. We need
+ * now to handle autorized request.
+ */
+
+ buffer_copy_string_buffer(con->physical.doc_root, host->docroot);
+
+ buffer_copy_string_buffer(con->physical.path, host->docroot);
+ buffer_append_string_buffer(con->physical.path, con->uri.path);
+ fcgi_connection_cleanup(srv, hctx);
+
+ con->mode = DIRECT;
+ con->file_started = 1; /* fcgi_extension won't touch the request afterwards */
+ } else {
+ /* we are done */
+ fcgi_connection_cleanup(srv, hctx);
+ }
+
+ return HANDLER_FINISHED;
+ case -1:
+ if (proc->pid) {
+ int status;
+ switch(waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child is still alive */
+ break;
+ case -1:
+ break;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "child exited, pid:", proc->pid,
+ "status:", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+
+ if (fcgi_spawn_connection(srv, p, host, proc)) {
+ /* child died */
+ proc->state = PROC_STATE_DIED;
+ } else {
+ fcgi_proclist_sort_down(srv, host, proc);
+ }
+
+ break;
+ }
+ }
+
+ if (con->file_started == 0) {
+ /* nothing has been send out yet, try to use another child */
+
+ if (hctx->write_offset == 0 &&
+ hctx->reconnects < 5) {
+ fcgi_reconnect(srv, hctx);
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsdsd",
+ "response not sent, request not sent, reconnection.",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsdsd",
+ "response not sent, request sent:", hctx->write_offset,
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ fcgi_connection_cleanup(srv, hctx);
+
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ buffer_reset(con->physical.path);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ } else {
+ /* response might have been already started, kill the connection */
+ fcgi_connection_cleanup(srv, hctx);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "response already sent out, termination connection",
+ "connection-fd:", con->fd,
+ "fcgi-fd:", hctx->fd);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+
+ /* */
+
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ if (hctx->state == FCGI_STATE_CONNECT ||
+ hctx->state == FCGI_STATE_WRITE) {
+ /* we are allowed to send something out
+ *
+ * 1. in a unfinished connect() call
+ * 2. in a unfinished write() call (long POST request)
+ */
+ return mod_fastcgi_handle_subrequest(srv, con, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "got a FDEVENT_OUT and didn't know why:",
+ hctx->state);
+ }
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ if (hctx->state == FCGI_STATE_CONNECT) {
+ /* getoptsock will catch this one (right ?)
+ *
+ * if we are in connect we might get a EINPROGRESS
+ * in the first call and a FDEVENT_HUP in the
+ * second round
+ *
+ * FIXME: as it is a bit ugly.
+ *
+ */
+ return mod_fastcgi_handle_subrequest(srv, con, p);
+ } else if (hctx->state == FCGI_STATE_READ &&
+ hctx->proc->port == 0) {
+ /* FIXME:
+ *
+ * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket
+ * even if the FCGI_FIN packet is not received yet
+ */
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbSBSDSd",
+ "error: unexpected close of fastcgi connection for",
+ con->uri.path,
+ "(no fastcgi process on host: ",
+ host->host,
+ ", port: ",
+ host->port,
+ " ?)",
+ hctx->state);
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ fcgi_connection_close(srv, hctx);
+ }
+ } else if (revents & FDEVENT_ERR) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "fcgi: got a FDEVENT_ERR. Don't know why.");
+ /* kill all connections to the fastcgi process */
+
+
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ fcgi_connection_close(srv, hctx);
+ }
+
+ return HANDLER_FINISHED;
+}
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.server"))) {
+ PATCH(exts);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.debug"))) {
+ PATCH(debug);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int fcgi_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(exts);
+ PATCH(debug);
+
+ return 0;
+}
+#undef PATCH
+
+
+static handler_t fcgi_check_extension(server *srv, connection *con, void *p_d, int uri_path_handler) {
+ plugin_data *p = p_d;
+ size_t s_len;
+ int used = -1;
+ int ndx;
+ size_t k, i;
+ buffer *fn;
+ fcgi_extension *extension = NULL;
+ size_t path_info_offset;
+
+ /* Possibly, we processed already this request */
+ if (con->file_started == 1) return HANDLER_GO_ON;
+
+ fn = uri_path_handler ? con->uri.path : con->physical.path;
+
+ if (fn->used == 0) {
+ return HANDLER_ERROR;
+ }
+
+ s_len = fn->used - 1;
+
+ /* select the right config */
+ fcgi_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ fcgi_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ path_info_offset = 0;
+
+ /* check if extension matches */
+ for (k = 0; k < p->conf.exts->used; k++) {
+ size_t ct_len;
+
+ extension = p->conf.exts->exts[k];
+
+ if (extension->key->used == 0) continue;
+
+ ct_len = extension->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ /* check extension in the form "/fcgi_pattern" */
+ if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) {
+ if (s_len > ct_len + 1) {
+ char *pi_offset;
+
+ if (0 != (pi_offset = strchr(fn->ptr + ct_len + 1, '/'))) {
+ path_info_offset = pi_offset - fn->ptr;
+ }
+ }
+ break;
+ } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) {
+ /* check extension in the form ".fcg" */
+ break;
+ }
+ }
+
+ /* extension doesn't match */
+ if (k == p->conf.exts->used) {
+ return HANDLER_GO_ON;
+ }
+
+ /* get best server */
+ for (k = 0, ndx = -1; k < extension->used; k++) {
+ fcgi_extension_host *host = extension->hosts[k];
+
+ /* we should have at least one proc that can do somthing */
+ if (host->active_procs == 0) continue;
+
+ if (used == -1 || host->load < used) {
+ used = host->load;
+
+ ndx = k;
+ }
+ }
+
+ /* found a server */
+ if (ndx != -1) {
+ fcgi_extension_host *host = extension->hosts[ndx];
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ if (uri_path_handler) {
+ if (host->check_local == 0) {
+ handler_ctx *hctx;
+ hctx = handler_ctx_init();
+
+ hctx->path_info_offset = path_info_offset;
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+ hctx->proc = NULL;
+
+ hctx->conf.exts = p->conf.exts;
+ hctx->conf.debug = p->conf.debug;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ host->load++;
+
+ con->mode = p->id;
+ }
+ return HANDLER_GO_ON;
+ } else {
+ handler_ctx *hctx;
+ hctx = handler_ctx_init();
+
+ hctx->path_info_offset = path_info_offset;
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+ hctx->proc = NULL;
+
+ hctx->conf.exts = p->conf.exts;
+ hctx->conf.debug = p->conf.debug;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ host->load++;
+
+ con->mode = p->id;
+
+ return HANDLER_FINISHED;
+ }
+ } else {
+ /* no handler found */
+ buffer_reset(con->physical.path);
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "no fcgi-handler found for:",
+ fn);
+
+ return HANDLER_FINISHED;
+ }
+ return HANDLER_GO_ON;
+}
+
+/* uri-path handler */
+static handler_t fcgi_check_extension_1(server *srv, connection *con, void *p_d) {
+ return fcgi_check_extension(srv, con, p_d, 1);
+}
+
+/* start request handler */
+static handler_t fcgi_check_extension_2(server *srv, connection *con, void *p_d) {
+ return fcgi_check_extension(srv, con, p_d, 0);
+}
+
+JOBLIST_FUNC(mod_fastcgi_handle_joblist) {
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+
+ if (hctx == NULL) return HANDLER_GO_ON;
+
+ if (hctx->fd != -1) {
+ switch (hctx->state) {
+ case FCGI_STATE_READ:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ break;
+ case FCGI_STATE_CONNECT:
+ case FCGI_STATE_WRITE:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ break;
+ case FCGI_STATE_INIT:
+ /* at reconnect */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled fcgi.state", hctx->state);
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static handler_t fcgi_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ return fcgi_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+TRIGGER_FUNC(mod_fastcgi_handle_trigger) {
+ plugin_data *p = p_d;
+ size_t i, j, n;
+
+
+ /* perhaps we should kill a connect attempt after 10-15 seconds
+ *
+ * currently we wait for the TCP timeout which is on Linux 180 seconds
+ *
+ *
+ *
+ */
+
+ /* check all childs if they are still up */
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *conf;
+ fcgi_exts *exts;
+
+ conf = p->config_storage[i];
+
+ exts = conf->exts;
+
+ for (j = 0; j < exts->used; j++) {
+ fcgi_extension *ex;
+
+ ex = exts->exts[j];
+
+ for (n = 0; n < ex->used; n++) {
+
+ fcgi_proc *proc;
+ unsigned long sum_load = 0;
+ fcgi_extension_host *host;
+
+ host = ex->hosts[n];
+
+ fcgi_restart_dead_procs(srv, p, host);
+
+ for (proc = host->first; proc; proc = proc->next) {
+ sum_load += proc->load;
+ }
+
+ if (host->num_procs &&
+ host->num_procs < host->max_procs &&
+ (sum_load / host->num_procs) > host->max_load_per_proc) {
+ /* overload, spawn new child */
+ fcgi_proc *fp = NULL;
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "overload detected, spawning a new child");
+ }
+
+ for (fp = host->unused_procs; fp && fp->pid != 0; fp = fp->next);
+
+ if (fp) {
+ if (fp == host->unused_procs) host->unused_procs = fp->next;
+
+ if (fp->next) fp->next->prev = NULL;
+
+ host->max_id++;
+ } else {
+ fp = fastcgi_process_init();
+ fp->id = host->max_id++;
+ }
+
+ host->num_procs++;
+
+ if (buffer_is_empty(host->unixsocket)) {
+ fp->port = host->port + fp->id;
+ } else {
+ buffer_copy_string_buffer(fp->socket, host->unixsocket);
+ buffer_append_string(fp->socket, "-");
+ buffer_append_long(fp->socket, fp->id);
+ }
+
+ if (fcgi_spawn_connection(srv, p, host, fp)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning fcgi failed.");
+ return HANDLER_ERROR;
+ }
+
+ fp->prev = NULL;
+ fp->next = host->first;
+ if (host->first) {
+ host->first->prev = fp;
+ }
+ host->first = fp;
+ }
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->load != 0) break;
+ if (host->num_procs <= host->min_procs) break;
+ if (proc->pid == 0) continue;
+
+ if (srv->cur_ts - proc->last_used > host->idle_timeout) {
+ /* a proc is idling for a long time now,
+ * terminated it */
+
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "idle-timeout reached, terminating child:",
+ "socket:", proc->socket,
+ "pid", proc->pid);
+ }
+
+
+ if (proc->next) proc->next->prev = proc->prev;
+ if (proc->prev) proc->prev->next = proc->next;
+
+ if (proc->prev == NULL) host->first = proc->next;
+
+ proc->prev = NULL;
+ proc->next = host->unused_procs;
+
+ if (host->unused_procs) host->unused_procs->prev = proc;
+ host->unused_procs = proc;
+
+ kill(proc->pid, SIGTERM);
+
+ proc->state = PROC_STATE_KILLED;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "killed:",
+ "socket:", proc->socket,
+ "pid", proc->pid);
+
+ host->num_procs--;
+
+ /* proc is now in unused, let the next second handle the next process */
+ break;
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ int status;
+
+ if (proc->pid == 0) continue;
+
+ switch (waitpid(proc->pid, &status, WNOHANG)) {
+ case 0:
+ /* child still running after timeout, good */
+ break;
+ case -1:
+ if (errno != EINTR) {
+ /* no PID found ? should never happen */
+ log_error_write(srv, __FILE__, __LINE__, "sddss",
+ "pid ", proc->pid, proc->state,
+ "not found:", strerror(errno));
+
+#if 0
+ if (errno == ECHILD) {
+ /* someone else has cleaned up for us */
+ proc->pid = 0;
+ proc->state = PROC_STATE_UNSET;
+ }
+#endif
+ }
+ break;
+ default:
+ /* the child should not terminate at all */
+ if (WIFEXITED(status)) {
+ if (proc->state != PROC_STATE_KILLED) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "child exited:",
+ WEXITSTATUS(status), proc->socket);
+ }
+ } else if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) != SIGTERM) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signaled:",
+ WTERMSIG(status));
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:",
+ status);
+ }
+ proc->pid = 0;
+ proc->state = PROC_STATE_UNSET;
+ host->max_id--;
+ }
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_fastcgi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("fastcgi");
+
+ p->init = mod_fastcgi_init;
+ p->cleanup = mod_fastcgi_free;
+ p->set_defaults = mod_fastcgi_set_defaults;
+ p->connection_reset = fcgi_connection_reset;
+ p->handle_connection_close = fcgi_connection_close_callback;
+ p->handle_uri_clean = fcgi_check_extension_1;
+ p->handle_subrequest_start = fcgi_check_extension_2;
+ p->handle_subrequest = mod_fastcgi_handle_subrequest;
+ p->handle_joblist = mod_fastcgi_handle_joblist;
+ p->handle_trigger = mod_fastcgi_handle_trigger;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_mysql_vhost.c b/src/mod_mysql_vhost.c
new file mode 100644
index 00000000..76ff96aa
--- /dev/null
+++ b/src/mod_mysql_vhost.c
@@ -0,0 +1,425 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <strings.h>
+
+#include "plugin.h"
+#include "config.h"
+#include "log.h"
+
+#include "file_cache.h"
+#ifdef HAVE_MYSQL
+#include <mysql/mysql.h>
+#endif
+#ifdef DEBUG_MOD_MYSQL_VHOST
+#define DEBUG
+#endif
+
+/*
+ * Plugin for lighttpd to use MySQL
+ * for domain to directory lookups,
+ * i.e virtual hosts (vhosts).
+ *
+ * Optionally sets fcgi_offset and fcgi_arg
+ * in preparation for fcgi.c to handle
+ * per-user fcgi chroot jails.
+ *
+ * /ada@riksnet.se 2004-12-06
+ */
+
+typedef struct {
+#ifdef HAVE_MYSQL
+ MYSQL *mysql;
+#endif
+ buffer *mydb;
+ buffer *myuser;
+ buffer *mypass;
+ buffer *mysock;
+
+ buffer *mysql_pre;
+ buffer *mysql_post;
+} plugin_config;
+
+/* global plugin data */
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *tmp_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* per connection plugin data */
+typedef struct {
+ buffer *server_name;
+ buffer *document_root;
+ buffer *fcgi_arg;
+ unsigned fcgi_offset;
+} plugin_connection_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_mysql_vhost_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->tmp_buf = buffer_init();
+
+ return p;
+}
+
+/* cleanup the plugin data */
+SERVER_FUNC(mod_mysql_vhost_cleanup) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "mod_mysql_vhost_cleanup", p ? "yes" : "NO");
+#endif
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+#ifdef HAVE_MYSQL
+ mysql_close(s->mysql);
+#endif
+ buffer_free(s->mydb);
+ buffer_free(s->myuser);
+ buffer_free(s->mypass);
+ buffer_free(s->mysock);
+ buffer_free(s->mysql_pre);
+ buffer_free(s->mysql_post);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+ buffer_free(p->tmp_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle the plugin per connection data */
+static void* mod_mysql_vhost_connection_data(server *srv, connection *con, void *p_d)
+{
+ plugin_data *p = p_d;
+ plugin_connection_data *c = con->plugin_ctx[p->id];
+
+ UNUSED(srv);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "mod_mysql_connection_data", c ? "old" : "NEW");
+#endif
+
+ if (c) return c;
+ c = calloc(1, sizeof(*c));
+
+ c->server_name = buffer_init();
+ c->document_root = buffer_init();
+ c->fcgi_arg = buffer_init();
+ c->fcgi_offset = 0;
+
+ return con->plugin_ctx[p->id] = c;
+}
+
+/* destroy the plugin per connection data */
+CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close) {
+ plugin_data *p = p_d;
+ plugin_connection_data *c = con->plugin_ctx[p->id];
+
+ UNUSED(srv);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "mod_mysql_vhost_handle_connection_close", c ? "yes" : "NO");
+#endif
+
+ if (!c) return HANDLER_GO_ON;
+
+ buffer_free(c->server_name);
+ buffer_free(c->document_root);
+ buffer_free(c->fcgi_arg);
+ c->fcgi_offset = 0;
+
+ free(c);
+
+ con->plugin_ctx[p->id] = NULL;
+ return HANDLER_GO_ON;
+}
+
+/* set configuration values */
+SERVER_FUNC(mod_mysql_vhost_set_defaults) {
+ plugin_data *p = p_d;
+
+ char *qmark;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "mysql-vhost.db", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.user", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.pass", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.sock", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "mysql-vhost.sql", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ buffer *sel;
+
+
+ s = malloc(sizeof(plugin_config));
+ s->mydb = buffer_init();
+ s->myuser = buffer_init();
+ s->mypass = buffer_init();
+ s->mysock = buffer_init();
+ sel = buffer_init();
+#ifdef HAVE_MYSQL
+ s->mysql = NULL;
+#endif
+
+ s->mysql_pre = buffer_init();
+ s->mysql_post = buffer_init();
+
+ cv[0].destination = s->mydb;
+ cv[1].destination = s->myuser;
+ cv[2].destination = s->mypass;
+ cv[3].destination = s->mysock;
+ cv[4].destination = sel;
+
+ p->config_storage[i] = s;
+
+ if (config_insert_values_global(srv,
+ ((data_config *)srv->config_context->data[i])->value,
+ cv)) return HANDLER_ERROR;
+
+ s->mysql_pre = buffer_init();
+ s->mysql_post = buffer_init();
+
+ if (sel->used && (qmark = index(sel->ptr, '?'))) {
+ *qmark = '\0';
+ buffer_copy_string(s->mysql_pre, sel->ptr);
+ buffer_copy_string(s->mysql_post, qmark+1);
+ } else {
+ buffer_copy_string_buffer(s->mysql_pre, sel);
+ }
+
+ /* all have to be set */
+ if (!(buffer_is_empty(s->myuser) ||
+ buffer_is_empty(s->mypass) ||
+ buffer_is_empty(s->mydb) ||
+ buffer_is_empty(s->mysock))) {
+#ifdef HAVE_MYSQL
+ int fd;
+
+ if (NULL == (s->mysql = mysql_init(NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting...");
+
+ return HANDLER_ERROR;
+ }
+
+ if (!mysql_real_connect(s->mysql, NULL, s->myuser->ptr, s->mypass->ptr,
+ s->mydb->ptr, 0, s->mysock->ptr, 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql));
+
+ return HANDLER_ERROR;
+ }
+
+ /* set close_on_exec for mysql the hard way */
+ /* Note: this only works as it is done during startup, */
+ /* otherwise we cannot be sure that mysql is fd i-1 */
+ if (-1 == (fd = open("/dev/null", 0))) {
+ close(fd);
+ fcntl(fd-1, F_SETFD, FD_CLOEXEC);
+ }
+#endif
+ }
+ }
+
+
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_mysql_vhost_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("mysql-vhost.sql"))) {
+ PATCH(mysql_pre);
+ PATCH(mysql_post);
+ }
+ }
+
+#ifdef HAVE_MYSQL
+ if (s->mysql) {
+ PATCH(mysql);
+ }
+#endif
+ }
+
+ return 0;
+}
+
+static int mod_mysql_vhost_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(mysql_pre);
+ PATCH(mysql_post);
+#ifdef HAVE_MYSQL
+ PATCH(mysql);
+#endif
+
+ return 0;
+}
+#undef PATCH
+
+
+/* handle document root request */
+CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
+#ifdef HAVE_MYSQL
+ plugin_data *p = p_d;
+ plugin_connection_data *c;
+
+ unsigned cols;
+ MYSQL_ROW row;
+ MYSQL_RES *result = NULL;
+ size_t i;
+
+ /* no host specified? */
+ if (!con->uri.authority->used) return HANDLER_GO_ON;
+
+ /* apply conditionals */
+ mod_mysql_vhost_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_mysql_vhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ /* sets up connection data if not done yet */
+ c = mod_mysql_vhost_connection_data(srv, con, p_d);
+
+ /* check if cached this connection */
+ if (c->server_name->used && /* con->uri.authority->used && */
+ buffer_is_equal(c->server_name, con->uri.authority)) goto GO_ON;
+
+ /* build and run SQL query */
+ buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre);
+ if (p->conf.mysql_post->used) {
+ buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
+ buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
+ }
+ if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql));
+ goto ERR500;
+ }
+ result = mysql_store_result(p->conf.mysql);
+ cols = mysql_num_fields(result);
+ row = mysql_fetch_row(result);
+ if (!row || cols < 1) {
+ /* no such virtual host */
+ mysql_free_result(result);
+ return HANDLER_GO_ON;
+ }
+
+ /* sanity check that really is a directory */
+ buffer_copy_string(p->tmp_buf, row[0]);
+ BUFFER_APPEND_SLASH(p->tmp_buf);
+ if (file_cache_get_entry(srv, con, p->tmp_buf, &(con->fce)) != HANDLER_GO_ON) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
+ goto ERR500;
+ }
+ if (!S_ISDIR(con->fce->st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", p->tmp_buf);
+ goto ERR500;
+ }
+
+ /* cache the data */
+ buffer_copy_string_buffer(c->server_name, con->uri.authority);
+ buffer_copy_string_buffer(c->document_root, p->tmp_buf);
+
+ /* fcgi_offset and fcgi_arg are optional */
+ if (cols > 1 && row[1]) {
+ c->fcgi_offset = atoi(row[1]);
+
+ if (cols > 2 && row[2]) {
+ buffer_copy_string(c->fcgi_arg, row[2]);
+ } else {
+ c->fcgi_arg->used = 0;
+ }
+ } else {
+ c->fcgi_offset = c->fcgi_arg->used = 0;
+ }
+ mysql_free_result(result);
+
+ /* fix virtual server and docroot */
+GO_ON: buffer_copy_string_buffer(con->server_name, c->server_name);
+ buffer_copy_string_buffer(con->physical.doc_root, c->document_root);
+
+#ifdef DEBUG
+ log_error_write(srv, __FILE__, __LINE__, "sbbdb",
+ result ? "NOT CACHED" : "cached",
+ con->server_name, con->physical.doc_root,
+ c->fcgi_offset, c->fcgi_arg);
+#endif
+ return HANDLER_GO_ON;
+
+ERR500: if (result) mysql_free_result(result);
+ con->http_status = 500; /* Internal Error */
+ return HANDLER_FINISHED;
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_d);
+
+ return HANDLER_ERROR;
+#endif
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+int mod_mysql_vhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("mysql_vhost");
+
+ p->init = mod_mysql_vhost_init;
+ p->cleanup = mod_mysql_vhost_cleanup;
+ p->handle_connection_close = mod_mysql_vhost_handle_connection_close;
+
+ p->set_defaults = mod_mysql_vhost_set_defaults;
+ p->handle_docroot = mod_mysql_vhost_handle_docroot;
+
+ return 0;
+}
+
diff --git a/src/mod_proxy.c b/src/mod_proxy.c
new file mode 100644
index 00000000..48bab9a4
--- /dev/null
+++ b/src/mod_proxy.c
@@ -0,0 +1,1093 @@
+#include <sys/types.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "buffer.h"
+#include "server.h"
+#include "keyvalue.h"
+#include "log.h"
+
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "response.h"
+#include "joblist.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+#include <stdio.h>
+
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include "sys-socket.h"
+
+#define data_proxy data_fastcgi
+#define data_proxy_init data_fastcgi_init
+
+#define PROXY_RETRY_TIMEOUT 60
+
+/**
+ *
+ * the proxy module is based on the fastcgi module
+ *
+ * 28.06.2004 Jan Kneschke The first release
+ * 01.07.2004 Evgeny Rodichev Several bugfixes and cleanups
+ * - co-ordinate up- and downstream flows correctly (proxy_demux_response
+ * and proxy_handle_fdevent)
+ * - correctly transfer upstream http_response_status;
+ * - some unused structures removed.
+ *
+ * TODO: - delay upstream read if write_queue is too large
+ * (to prevent memory eating, like in apache). Shoud be
+ * configurable).
+ * - persistent connection with upstream servers
+ * - HTTP/1.1
+ */
+
+typedef struct {
+ array *extensions;
+ int debug;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *parse_response;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef enum { PROXY_STATE_INIT, PROXY_STATE_CONNECT, PROXY_STATE_PREPARE_WRITE, PROXY_STATE_WRITE, PROXY_STATE_READ, PROXY_STATE_ERROR } proxy_connection_state_t;
+enum { PROXY_STDOUT, PROXY_END_REQUEST };
+typedef struct {
+ proxy_connection_state_t state;
+ time_t state_timestamp;
+
+ data_proxy *host;
+
+ buffer *response;
+ buffer *response_header;
+
+ buffer *write_buffer;
+ size_t write_offset;
+
+
+ int fd; /* fd to the proxy process */
+ int fde_ndx; /* index into the fd-event buffer */
+
+ size_t path_info_offset; /* start of path_info in uri.path */
+
+ connection *remote_conn; /* dump pointer */
+ plugin_data *plugin_data; /* dump pointer */
+} handler_ctx;
+
+
+/* ok, we need a prototype */
+static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents);
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ hctx->state = PROXY_STATE_INIT;
+ hctx->host = NULL;
+
+ hctx->response = buffer_init();
+ hctx->response_header = buffer_init();
+
+ hctx->write_buffer = buffer_init();
+
+ hctx->fd = -1;
+ hctx->fde_ndx = -1;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ buffer_free(hctx->response);
+ buffer_free(hctx->response_header);
+ buffer_free(hctx->write_buffer);
+
+ free(hctx);
+}
+
+INIT_FUNC(mod_proxy_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->parse_response = buffer_init();
+
+ return p;
+}
+
+
+FREE_FUNC(mod_proxy_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ buffer_free(p->parse_response);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (s) {
+
+ array_free(s->extensions);
+
+ free(s);
+ }
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_proxy_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "proxy.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "proxy.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ array *ca;
+
+ s = malloc(sizeof(plugin_config));
+ s->extensions = array_init();
+ s->debug = 0;
+
+ cv[0].destination = s->extensions;
+ cv[1].destination = &(s->debug);
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (NULL != (du = array_get_element(ca, "proxy.server"))) {
+ size_t j;
+ data_array *da = (data_array *)du;
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "proxy.server", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * proxy.server = ( "<ext>" => ...,
+ * "<ext>" => ... )
+ */
+
+ for (j = 0; j < da->value->used; j++) {
+ data_array *da_ext = (data_array *)da->value->data[j];
+ size_t n;
+
+ if (da_ext->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ", "proxy.server",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * proxy.server = ( "<ext>" =>
+ * ( "<host>" => ( ... ),
+ * "<host>" => ( ... )
+ * ),
+ * "<ext>" => ... )
+ */
+
+ for (n = 0; n < da_ext->value->used; n++) {
+ data_array *da_host = (data_array *)da_ext->value->data[n];
+
+ data_proxy *df;
+ data_array *dfa;
+
+ config_values_t pcv[] = {
+ { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (da_host->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "ssSBS",
+ "unexpected type for key:",
+ "proxy.server",
+ "[", da_ext->value->data[n]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ df = data_proxy_init();
+
+ buffer_copy_string_buffer(df->key, da_host->key);
+
+ pcv[0].destination = df->host;
+ pcv[1].destination = &(df->port);
+
+ if (0 != config_insert_values_internal(srv, da_host->value, pcv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_is_empty(df->host)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (string):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "host");
+
+ return HANDLER_ERROR;
+ } else if (df->port == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbs",
+ "missing key (short):",
+ da->key,
+ da_ext->key,
+ da_host->key,
+ "port");
+ return HANDLER_ERROR;
+ }
+
+ /* if extension already exists, take it */
+
+ if (NULL == (dfa = (data_array *)array_get_element(s->extensions, da_ext->key->ptr))) {
+ dfa = data_array_init();
+
+ buffer_copy_string_buffer(dfa->key, da_ext->key);
+
+ array_insert_unique(dfa->value, (data_unset *)df);
+ array_insert_unique(s->extensions, (data_unset *)dfa);
+ } else {
+ array_insert_unique(dfa->value, (data_unset *)df);
+ }
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+void proxy_connection_cleanup(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) return;
+
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ fdevent_unregister(srv->ev, hctx->fd);
+ if (hctx->fd != -1) {
+ close(hctx->fd);
+ srv->cur_fds--;
+ }
+
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+}
+
+static handler_t mod_proxy_connection_reset(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ proxy_connection_cleanup(srv, con->plugin_ctx[p->id]);
+
+ return HANDLER_GO_ON;
+}
+
+static int proxy_establish_connection(server *srv, handler_ctx *hctx) {
+ struct sockaddr *proxy_addr;
+ struct sockaddr_in proxy_addr_in;
+ socklen_t servlen;
+
+ plugin_data *p = hctx->plugin_data;
+ data_proxy *host= hctx->host;
+ int proxy_fd = hctx->fd;
+
+ memset(&proxy_addr, 0, sizeof(proxy_addr));
+
+ proxy_addr_in.sin_family = AF_INET;
+ proxy_addr_in.sin_addr.s_addr = inet_addr(host->host->ptr);
+ proxy_addr_in.sin_port = htons(host->port);
+ servlen = sizeof(proxy_addr_in);
+
+ proxy_addr = (struct sockaddr *) &proxy_addr_in;
+
+ if (-1 == connect(proxy_fd, proxy_addr, servlen)) {
+ if (errno == EINPROGRESS || errno == EALREADY) {
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect delayed:", proxy_fd);
+ }
+
+ return -proxy_fd;
+ } else {
+
+ log_error_write(srv, __FILE__, __LINE__, "sdsd",
+ "connect failed:", proxy_fd, strerror(errno), errno);
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return -1;
+ }
+ }
+ if (p->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", proxy_fd);
+ }
+ return proxy_fd;
+}
+
+static int proxy_create_env(server *srv, handler_ctx *hctx) {
+ size_t i;
+
+ connection *con = hctx->remote_conn;
+ UNUSED(srv);
+
+ /* build header */
+
+ buffer_reset(hctx->write_buffer);
+
+ /* request line */
+ switch(con->request.http_method) {
+ case HTTP_METHOD_GET:
+ BUFFER_COPY_STRING_CONST(hctx->write_buffer, "GET ");
+ break;
+ case HTTP_METHOD_POST:
+ BUFFER_COPY_STRING_CONST(hctx->write_buffer, "POST ");
+ break;
+ case HTTP_METHOD_HEAD:
+ BUFFER_COPY_STRING_CONST(hctx->write_buffer, "HEAD ");
+ break;
+ default:
+ return -1;
+ }
+
+ buffer_append_string_buffer(hctx->write_buffer, con->request.uri);
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, " HTTP/1.0\r\n");
+
+ /* request header */
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ if (0 == strcmp(ds->key->ptr, "Connection")) continue;
+
+ buffer_append_string_buffer(hctx->write_buffer, ds->key);
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, ": ");
+ buffer_append_string_buffer(hctx->write_buffer, ds->value);
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n");
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "X-Forwarded-For: ");
+ buffer_append_string(hctx->write_buffer, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n");
+
+ BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n");
+
+ /* body */
+
+ if (con->request.http_method == HTTP_METHOD_POST &&
+ con->request.content_length) {
+ /* the buffer-string functions add an extra \0 at the end the memory-function don't */
+ hctx->write_buffer->used--;
+ buffer_append_memory(hctx->write_buffer, con->request.content->ptr, con->request.content_length);
+ }
+
+ return 0;
+}
+
+static int proxy_set_state(server *srv, handler_ctx *hctx, proxy_connection_state_t state) {
+ hctx->state = state;
+ hctx->state_timestamp = srv->cur_ts;
+
+ return 0;
+}
+
+
+static int proxy_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
+ char *s, *ns;
+ int http_response_status = -1;
+
+ UNUSED(srv);
+
+ /* \r\n -> \0\0 */
+
+ buffer_copy_string_buffer(p->parse_response, in);
+
+ for (s = p->parse_response->ptr; NULL != (ns = strstr(s, "\r\n")); s = ns + 2) {
+ char *key, *value;
+ int key_len;
+ data_string *ds;
+
+ ns[0] = '\0';
+ ns[1] = '\0';
+
+ if (-1 == http_response_status) {
+ /* The first line of a Response message is the Status-Line */
+
+ for (key=s; *key && *key != ' '; key++);
+
+ if (*key) {
+ http_response_status = (int) strtol(key, NULL, 10);
+ if (http_response_status <= 0) http_response_status = 502;
+ } else {
+ http_response_status = 502;
+ }
+
+ con->http_status = http_response_status;
+ con->parsed_response |= HTTP_STATUS;
+ continue;
+ }
+
+ if (NULL == (value = strchr(s, ':'))) {
+ /* now we expect: "<key>: <value>\n" */
+
+ continue;
+ }
+
+ key = s;
+ key_len = value - key;
+
+ value++;
+ /* strip WS */
+ while (*value == ' ' || *value == '\t') value++;
+
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string(ds->value, value);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ switch(key_len) {
+ case 4:
+ if (0 == strncasecmp(key, "Date", key_len)) {
+ con->parsed_response |= HTTP_DATE;
+ }
+ break;
+ case 8:
+ if (0 == strncasecmp(key, "Location", key_len)) {
+ con->parsed_response |= HTTP_LOCATION;
+ }
+ break;
+ case 10:
+ if (0 == strncasecmp(key, "Connection", key_len)) {
+ con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0;
+ con->parsed_response |= HTTP_CONNECTION;
+ }
+ break;
+ case 14:
+ if (0 == strncasecmp(key, "Content-Length", key_len)) {
+ con->response.content_length = strtol(value, NULL, 10);
+ con->parsed_response |= HTTP_CONTENT_LENGTH;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static int proxy_demux_response(server *srv, handler_ctx *hctx) {
+ int fin = 0;
+ int b;
+ ssize_t r;
+
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+ int proxy_fd = hctx->fd;
+
+ /* check how much we have to read */
+ if (ioctl(hctx->fd, FIONREAD, &b)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "ioctl failed: ",
+ proxy_fd);
+ return -1;
+ }
+
+ if (b > 0) {
+ if (hctx->response->used == 0) {
+ /* avoid too small buffer */
+ buffer_prepare_append(hctx->response, b + 1);
+ hctx->response->used = 1;
+ } else {
+ buffer_prepare_append(hctx->response, hctx->response->used + b);
+ }
+
+ if (-1 == (r = read(hctx->fd, hctx->response->ptr + hctx->response->used - 1, b))) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "unexpected end-of-file (perhaps the proxy process died):",
+ proxy_fd, strerror(errno));
+ return -1;
+ }
+
+ /* this should be catched by the b > 0 above */
+ assert(r);
+
+ hctx->response->used += r;
+ hctx->response->ptr[hctx->response->used - 1] = '\0';
+
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdsbs",
+ "demux: Response buffer len", hctx->response->used, ":", hctx->response, ":");
+#endif
+
+ if (0 == con->got_response) {
+ con->got_response = 1;
+ buffer_prepare_copy(hctx->response_header, 128);
+ }
+
+ if (0 == con->file_started) {
+ char *c;
+
+ /* search for the \r\n\r\n in the string */
+ if (NULL != (c = buffer_search_string_len(hctx->response, "\r\n\r\n", 4))) {
+ size_t hlen = c - hctx->response->ptr + 4;
+ size_t blen = hctx->response->used - hlen - 1;
+ /* found */
+
+ buffer_append_string_len(hctx->response_header, hctx->response->ptr, c - hctx->response->ptr + 4);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Header:", hctx->response_header);
+#endif
+ /* parse the response header */
+ proxy_response_parse(srv, con, p, hctx->response_header);
+
+ /* enable chunked-transfer-encoding */
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ !(con->parsed_response & HTTP_CONTENT_LENGTH)) {
+ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
+ }
+
+ con->file_started = 1;
+ if (blen) {
+ http_chunk_append_mem(srv, con, c + 4, blen + 1);
+ joblist_append(srv, con);
+ }
+ hctx->response->used = 0;
+ }
+ } else {
+ http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
+ joblist_append(srv, con);
+ hctx->response->used = 0;
+ }
+
+ } else {
+ /* reading from upstream done */
+ con->file_finished = 1;
+
+ http_chunk_append_mem(srv, con, NULL, 0);
+ joblist_append(srv, con);
+
+ fin = 1;
+ }
+
+ return fin;
+}
+
+
+static int proxy_write_request(server *srv, handler_ctx *hctx) {
+ data_proxy *host= hctx->host;
+
+ int r;
+
+ if (!host ||
+ (!host->host->used || !host->port)) return -1;
+
+ switch(hctx->state) {
+ case PROXY_STATE_INIT:
+ r = AF_INET;
+
+ if (-1 == (hctx->fd = socket(r, SOCK_STREAM, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed: ", strerror(errno));
+ return -1;
+ }
+ hctx->fde_ndx = -1;
+
+ srv->cur_fds++;
+
+ fdevent_register(srv->ev, hctx->fd, proxy_handle_fdevent, hctx);
+
+ if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return -1;
+ }
+
+ proxy_set_state(srv, hctx, PROXY_STATE_CONNECT);
+ /* fall through */
+
+ case PROXY_STATE_CONNECT:
+ /* try to finish the connect() */
+ hctx->fd = proxy_establish_connection(srv, hctx);
+
+ if (hctx->fd == -1) {
+ hctx->fde_ndx = -1;
+ return -1;
+ }
+
+ if (hctx->fd < 0) {
+ hctx->fd = -hctx->fd;
+ proxy_set_state(srv, hctx, PROXY_STATE_CONNECT);
+ return 0;
+ }
+
+ proxy_set_state(srv, hctx, PROXY_STATE_PREPARE_WRITE);
+ /* fall through */
+ case PROXY_STATE_PREPARE_WRITE:
+ proxy_create_env(srv, hctx);
+
+ proxy_set_state(srv, hctx, PROXY_STATE_WRITE);
+ hctx->write_offset = 0;
+
+ /* fall through */
+ case PROXY_STATE_WRITE:
+ /* continue with the code after the switch */
+ if (-1 == (r = write(hctx->fd,
+ hctx->write_buffer->ptr + hctx->write_offset,
+ hctx->write_buffer->used - hctx->write_offset))) {
+ if (errno != EAGAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), r);
+
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ hctx->write_offset += r;
+
+ if (hctx->write_offset == hctx->write_buffer->used) {
+ proxy_set_state(srv, hctx, PROXY_STATE_READ);
+ }
+
+ break;
+ case PROXY_STATE_READ:
+ /* waiting for a response */
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return -1;
+ }
+
+ return 0;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_proxy_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.server"))) {
+ PATCH(extensions);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.debug"))) {
+ PATCH(debug);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_proxy_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(extensions);
+ PATCH(debug);
+
+ return 0;
+}
+#undef PATCH
+
+
+SUBREQUEST_FUNC(mod_proxy_handle_subrequest) {
+ plugin_data *p = p_d;
+
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ data_proxy *host;
+ size_t i;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ /* select the right config */
+ mod_proxy_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_proxy_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ host = hctx->host;
+
+ /* not my job */
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ /* ok, create the request */
+ if (-1 == proxy_write_request(srv, hctx)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:",
+ host->host,
+ host->port,
+ hctx->fd);
+
+ /* disable this server */
+ host->usage = -1;
+ host->disable_ts = srv->cur_ts;
+
+ con->mode = DIRECT;
+ con->http_status = 503;
+ return HANDLER_FINISHED;
+ }
+
+ if (con->file_started == 1) {
+ return HANDLER_FINISHED;
+ } else {
+ return HANDLER_WAIT_FOR_EVENT;
+ }
+}
+
+static handler_t proxy_connection_close(server *srv, handler_ctx *hctx) {
+ plugin_data *p;
+ connection *con;
+
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ p = hctx->plugin_data;
+ con = hctx->remote_conn;
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdsd",
+ "emergency exit: proxy:",
+ "connection-fd:", con->fd,
+ "proxy-fd:", hctx->fd);
+
+
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return HANDLER_FINISHED;
+}
+
+
+static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents) {
+ server *srv = (server *)s;
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+ plugin_data *p = hctx->plugin_data;
+
+ joblist_append(srv, con);
+
+ if ((revents & FDEVENT_IN) &&
+ hctx->state == PROXY_STATE_READ) {
+ switch (proxy_demux_response(srv, hctx)) {
+ case 0:
+ break;
+ case 1:
+ hctx->host->usage--;
+
+ /* we are done */
+
+ if (chunkqueue_is_empty(con->write_queue)) {
+ connection_set_state(srv, con, CON_STATE_RESPONSE_END);
+ }
+
+ proxy_connection_cleanup(srv, hctx);
+
+ return HANDLER_FINISHED;
+ case -1:
+ if (con->file_started == 0) {
+ /* nothing has been send out yet, send a 500 */
+ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
+ con->http_status = 500;
+ con->mode = DIRECT;
+ } else {
+ /* response might have been already started, kill the connection */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ if (revents & FDEVENT_OUT) {
+ if (hctx->state == PROXY_STATE_CONNECT ||
+ hctx->state == PROXY_STATE_WRITE) {
+ /* we are allowed to send something out
+ *
+ * 1. in a unfinished connect() call
+ * 2. in a unfinished write() call (long POST request)
+ */
+ return mod_proxy_handle_subrequest(srv, con, p);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "proxy: out", hctx->state);
+ }
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & FDEVENT_HUP) {
+ log_error_write(srv, __FILE__, __LINE__, "sbSBSDS",
+ "error: unexpected close of proxy connection for",
+ con->uri.path,
+ "(no proxy process on host: ",
+ hctx->host->host,
+ ", port: ",
+ hctx->host->port,
+ " ?)" );
+
+#ifndef USE_LINUX_SIGIO
+ proxy_connection_close(srv, hctx);
+# if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd", "proxy-FDEVENT_HUP", con->fd);
+# endif
+ return HANDLER_ERROR;
+#endif
+ } else if (revents & FDEVENT_ERR) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy: err");
+ /* kill all connections to the proxy process */
+
+ proxy_connection_close(srv, hctx);
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "s", "proxy-FDEVENT_ERR");
+#endif
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_FINISHED;
+}
+
+static handler_t mod_proxy_check_extension(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ size_t s_len;
+ int used = -1;
+ int ndx;
+ size_t k, i;
+ buffer *fn;
+ data_array *extension = NULL;
+ size_t path_info_offset;
+
+ /* Possibly, we processed already this request */
+ if (con->file_started == 1) return HANDLER_GO_ON;
+
+ /* select the right config */
+ mod_proxy_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_proxy_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ fn = con->uri.path;
+
+ if (fn->used == 0) {
+ return HANDLER_ERROR;
+ }
+
+ s_len = fn->used - 1;
+
+
+ path_info_offset = 0;
+
+ /* check if extension matches */
+ for (k = 0; k < p->conf.extensions->used; k++) {
+ size_t ct_len;
+
+ extension = (data_array *)p->conf.extensions->data[k];
+
+ if (extension->key->used == 0) continue;
+
+ ct_len = extension->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ /* check extension in the form "/proxy_pattern" */
+ if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) {
+ if (s_len > ct_len + 1) {
+ char *pi_offset;
+
+ if (0 != (pi_offset = strchr(fn->ptr + ct_len + 1, '/'))) {
+ path_info_offset = pi_offset - fn->ptr;
+ }
+ }
+ break;
+ } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) {
+ /* check extension in the form ".fcg" */
+ break;
+ }
+ }
+
+ /* extension doesn't match */
+ if (k == p->conf.extensions->used) {
+ return HANDLER_GO_ON;
+ }
+
+ /* get best server */
+ for (k = 0, ndx = -1; k < extension->value->used; k++) {
+ data_proxy *host = (data_proxy *)extension->value->data[k];
+
+ /* enable the server again, perhaps it is back again */
+ if ((host->usage == -1) &&
+ (srv->cur_ts - host->disable_ts > PROXY_RETRY_TIMEOUT)) {
+ host->usage = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbd", "proxy-server re-enabled:",
+ host->host, host->port);
+ }
+
+ if (used == -1 || host->usage < used) {
+ used = host->usage;
+
+ ndx = k;
+ }
+ }
+
+ /* found a server */
+ if (ndx != -1) {
+ data_proxy *host = (data_proxy *)extension->value->data[ndx];
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ handler_ctx *hctx;
+ hctx = handler_ctx_init();
+
+ hctx->path_info_offset = path_info_offset;
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ host->usage++;
+
+ con->mode = p->id;
+
+ return HANDLER_GO_ON;
+ } else {
+ /* no handler found */
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "no proxy-handler found for:",
+ fn);
+
+ return HANDLER_FINISHED;
+ }
+ return HANDLER_GO_ON;
+}
+
+JOBLIST_FUNC(mod_proxy_handle_joblist) {
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+
+ if (hctx == NULL) return HANDLER_GO_ON;
+
+ if (hctx->fd != -1) {
+ switch (hctx->state) {
+ case PROXY_STATE_READ:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+
+ break;
+ case PROXY_STATE_CONNECT:
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled proxy.state", hctx->state);
+ break;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static handler_t mod_proxy_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+
+ return proxy_connection_close(srv, con->plugin_ctx[p->id]);
+}
+
+int mod_proxy_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("proxy");
+
+ p->init = mod_proxy_init;
+ p->cleanup = mod_proxy_free;
+ p->set_defaults = mod_proxy_set_defaults;
+ p->connection_reset = mod_proxy_connection_reset;
+ p->handle_connection_close = mod_proxy_connection_close_callback;
+ p->handle_uri_clean = mod_proxy_check_extension;
+ p->handle_subrequest = mod_proxy_handle_subrequest;
+ p->handle_joblist = mod_proxy_handle_joblist;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_redirect.c b/src/mod_redirect.c
new file mode 100644
index 00000000..5c5ecf25
--- /dev/null
+++ b/src/mod_redirect.c
@@ -0,0 +1,271 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+#include "response.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef struct {
+ pcre_keyvalue_buffer *redirect;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *match_buf;
+ buffer *location;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_redirect_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->match_buf = buffer_init();
+ p->location = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_redirect_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->match_buf);
+ buffer_free(p->location);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_redirect_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.redirect", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ /* 0 */
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ size_t j;
+ array *ca;
+ data_array *da = (data_array *)du;
+
+ s = malloc(sizeof(plugin_config));
+ s->redirect = pcre_keyvalue_buffer_init();
+
+ cv[0].destination = s->redirect;
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (NULL == (du = array_get_element(ca, "url.redirect"))) {
+ /* no url.redirect defined */
+ continue;
+ }
+
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "url.redirect", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ",
+ "url.redirect",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ if (0 != pcre_keyvalue_buffer_append(s->redirect,
+ ((data_string *)(da->value->data[j]))->key->ptr,
+ ((data_string *)(da->value->data[j]))->value->ptr)) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pcre-compile failed for", da->value->data[j]->key);
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+#ifdef HAVE_PCRE_H
+static int mod_redirect_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (0 == strcmp(du->key->ptr, "url.redirect")) {
+ p->conf.redirect = s->redirect;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_redirect_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ p->conf.redirect = s->redirect;
+
+ return 0;
+}
+#endif
+static handler_t mod_redirect_uri_handler(server *srv, connection *con, void *p_data) {
+#ifdef HAVE_PCRE_H
+ plugin_data *p = p_data;
+ size_t i;
+
+ /*
+ * REWRITE URL
+ *
+ * e.g. redirect /base/ to /index.php?section=base
+ *
+ */
+
+ mod_redirect_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_redirect_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ buffer_copy_string_buffer(p->match_buf, con->request.uri);
+
+ for (i = 0; i < p->conf.redirect->used; i++) {
+ pcre *match;
+ const char *pattern;
+ size_t pattern_len;
+ int n;
+# define N 10
+ int ovec[N * 3];
+
+ match = p->conf.redirect->kv[i]->key;
+ pattern = p->conf.redirect->kv[i]->value;
+ pattern_len = strlen(pattern);
+
+ if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd"
+ "execution error while matching: ", n);
+ return HANDLER_ERROR;
+ }
+ } else {
+ const char **list;
+ size_t start, end;
+ size_t k;
+ /* it matched */
+ pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
+
+ /* search for $[0-9] */
+
+ buffer_reset(p->location);
+
+ start = 0; end = pattern_len;
+ for (k = 0; k < pattern_len; k++) {
+ if (pattern[k] == '$' &&
+ isdigit((unsigned char)pattern[k + 1])) {
+ /* got one */
+
+ size_t num = pattern[k + 1] - '0';
+
+ end = k;
+
+ buffer_append_string_len(p->location, pattern + start, end - start);
+
+ /* n is always > 0 */
+ if (num < (size_t)n) {
+ buffer_append_string(p->location, list[num]);
+ }
+
+ k++;
+ start = k + 1;
+ }
+ }
+
+ buffer_append_string_len(p->location, pattern + start, pattern_len - start);
+
+ pcre_free(list);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->location));
+
+ con->http_status = 301;
+
+ return HANDLER_FINISHED;
+ }
+ }
+#undef N
+
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_data);
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_redirect_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("redirect");
+
+ p->init = mod_redirect_init;
+ p->handle_uri_clean = mod_redirect_uri_handler;
+ p->set_defaults = mod_redirect_set_defaults;
+ p->cleanup = mod_redirect_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_rewrite.c b/src/mod_rewrite.c
new file mode 100644
index 00000000..868abb5f
--- /dev/null
+++ b/src/mod_rewrite.c
@@ -0,0 +1,453 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef struct {
+#ifdef HAVE_PCRE_H
+ pcre *key;
+#endif
+
+ buffer *value;
+
+ int final;
+} rewrite_rule;
+
+typedef struct {
+ rewrite_rule **ptr;
+
+ size_t used;
+ size_t size;
+} rewrite_rule_buffer;
+
+typedef struct {
+ rewrite_rule_buffer *rewrite;
+} plugin_config;
+
+typedef struct {
+ enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
+} handler_ctx;
+
+typedef struct {
+ PLUGIN_DATA;
+ buffer *match_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ hctx->state = REWRITE_STATE_UNSET;
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+ free(hctx);
+}
+
+rewrite_rule_buffer *rewrite_rule_buffer_init(void) {
+ rewrite_rule_buffer *kvb;
+
+ kvb = calloc(1, sizeof(*kvb));
+
+ return kvb;
+}
+
+int rewrite_rule_buffer_append(rewrite_rule_buffer *kvb, buffer *key, buffer *value, int final) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+ const char *errptr;
+ int erroff;
+
+ if (!key) return -1;
+
+ if (kvb->size == 0) {
+ kvb->size = 4;
+ kvb->used = 0;
+
+ kvb->ptr = malloc(kvb->size * sizeof(*kvb->ptr));
+
+ for(i = 0; i < kvb->size; i++) {
+ kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
+ }
+ } else if (kvb->used == kvb->size) {
+ kvb->size += 4;
+
+ kvb->ptr = realloc(kvb->ptr, kvb->size * sizeof(*kvb->ptr));
+
+ for(i = kvb->used; i < kvb->size; i++) {
+ kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
+ }
+ }
+
+ if (NULL == (kvb->ptr[kvb->used]->key = pcre_compile(key->ptr,
+ 0, &errptr, &erroff, NULL))) {
+
+ return -1;
+ }
+
+ kvb->ptr[kvb->used]->value = buffer_init();
+ buffer_copy_string_buffer(kvb->ptr[kvb->used]->value, value);
+ kvb->ptr[kvb->used]->final = final;
+
+ kvb->used++;
+
+ return 0;
+#else
+ UNUSED(kvb);
+ UNUSED(value);
+ UNUSED(final);
+ UNUSED(key);
+
+ return -1;
+#endif
+}
+
+void rewrite_rule_buffer_free(rewrite_rule_buffer *kvb) {
+#ifdef HAVE_PCRE_H
+ size_t i;
+
+ for (i = 0; i < kvb->size; i++) {
+ if (kvb->ptr[i]->key) pcre_free(kvb->ptr[i]->key);
+ if (kvb->ptr[i]->value) buffer_free(kvb->ptr[i]->value);
+ free(kvb->ptr[i]);
+ }
+
+ if (kvb->ptr) free(kvb->ptr);
+#endif
+
+ free(kvb);
+}
+
+
+INIT_FUNC(mod_rewrite_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->match_buf = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_rewrite_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->match_buf);
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ rewrite_rule_buffer_free(s->rewrite);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
+ plugin_data *p = p_d;
+ data_unset *du;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ /* 0 */
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+ size_t j;
+ array *ca;
+ data_array *da = (data_array *)du;
+
+ s = malloc(sizeof(plugin_config));
+ s->rewrite = rewrite_rule_buffer_init();
+
+ cv[0].destination = s->rewrite;
+ cv[1].destination = s->rewrite;
+
+ p->config_storage[i] = s;
+ ca = ((data_config *)srv->config_context->data[i])->value;
+
+ if (0 != config_insert_values_global(srv, ca, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (NULL != (du = array_get_element(ca, "url.rewrite"))) {
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "url.rewrite", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ",
+ "url.rewrite",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ if (0 != rewrite_rule_buffer_append(s->rewrite,
+ ((data_string *)(da->value->data[j]))->key,
+ ((data_string *)(da->value->data[j]))->value,
+ 0)) {
+#ifdef HAVE_PCRE_H
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pcre-compile failed for", da->value->data[j]->key);
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "pcre support is missing, please install libpcre and the headers");
+#endif
+ }
+ }
+ }
+
+ if (NULL != (du = array_get_element(ca, "url.rewrite-final"))) {
+ if (du->type != TYPE_ARRAY) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "unexpected type for key: ", "url.rewrite", "array of strings");
+
+ return HANDLER_ERROR;
+ }
+
+ da = (data_array *)du;
+
+ for (j = 0; j < da->value->used; j++) {
+ if (da->value->data[j]->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "sssbs",
+ "unexpected type for key: ",
+ "url.rewrite",
+ "[", da->value->data[j]->key, "](string)");
+
+ return HANDLER_ERROR;
+ }
+
+ if (0 != rewrite_rule_buffer_append(s->rewrite,
+ ((data_string *)(da->value->data[j]))->key,
+ ((data_string *)(da->value->data[j]))->value,
+ 1)) {
+
+#ifdef HAVE_PCRE_H
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pcre-compile failed for", da->value->data[j]->key);
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "pcre support is missing, please install libpcre and the headers");
+#endif
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+#ifdef HAVE_PCRE_H
+static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
+ p->conf.rewrite = s->rewrite;
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
+ p->conf.rewrite = s->rewrite;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_rewrite_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ p->conf.rewrite = s->rewrite;
+
+ return 0;
+}
+#endif
+URIHANDLER_FUNC(mod_rewrite_con_reset) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (con->plugin_ctx[p->id]) {
+ handler_ctx_free(con->plugin_ctx[p->id]);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+URIHANDLER_FUNC(mod_rewrite_uri_handler) {
+#ifdef HAVE_PCRE_H
+ plugin_data *p = p_d;
+ size_t i;
+ handler_ctx *hctx;
+
+ /*
+ * REWRITE URL
+ *
+ * e.g. rewrite /base/ to /index.php?section=base
+ *
+ */
+
+ if (con->plugin_ctx[p->id]) {
+ hctx = con->plugin_ctx[p->id];
+
+ if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
+ }
+
+ mod_rewrite_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_rewrite_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ buffer_copy_string_buffer(p->match_buf, con->request.uri);
+
+ for (i = 0; i < p->conf.rewrite->used; i++) {
+ pcre *match;
+ const char *pattern;
+ size_t pattern_len;
+ int n;
+ rewrite_rule *rule = p->conf.rewrite->ptr[i];
+# define N 10
+ int ovec[N * 3];
+
+ match = rule->key;
+ pattern = rule->value->ptr;
+ pattern_len = rule->value->used - 1;
+
+ if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) {
+ if (n != PCRE_ERROR_NOMATCH) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching: ", n);
+ return HANDLER_ERROR;
+ }
+ } else {
+ const char **list;
+ size_t start, end;
+ size_t k;
+
+ /* it matched */
+ pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
+
+ /* search for $[0-9] */
+
+ buffer_reset(con->request.uri);
+
+ start = 0; end = pattern_len;
+ for (k = 0; k < pattern_len; k++) {
+ if (pattern[k] == '$' &&
+ isdigit((unsigned char)pattern[k + 1])) {
+ /* got one */
+
+ size_t num = pattern[k + 1] - '0';
+
+ end = k;
+
+ buffer_append_string_len(con->request.uri, pattern + start, end - start);
+
+ /* n is always larger than 0 */
+ if (num < (size_t)n) {
+ buffer_append_string(con->request.uri, list[num]);
+ }
+
+ k++;
+ start = k + 1;
+ }
+ }
+
+ buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start);
+
+ pcre_free(list);
+
+ hctx = handler_ctx_init();
+
+ con->plugin_ctx[p->id] = hctx;
+
+ if (rule->final) hctx->state = REWRITE_STATE_FINISHED;
+
+ return HANDLER_COMEBACK;
+ }
+ }
+#undef N
+
+#else
+ UNUSED(srv);
+ UNUSED(con);
+ UNUSED(p_d);
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+int mod_rewrite_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("rewrite");
+
+ p->init = mod_rewrite_init;
+ p->handle_uri_raw = mod_rewrite_uri_handler;
+ p->set_defaults = mod_rewrite_set_defaults;
+ p->cleanup = mod_rewrite_free;
+ p->connection_reset = mod_rewrite_con_reset;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_rrdtool.c b/src/mod_rrdtool.c
new file mode 100644
index 00000000..1719d3ca
--- /dev/null
+++ b/src/mod_rrdtool.c
@@ -0,0 +1,457 @@
+#define _GNU_SOURCE
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include "server.h"
+#include "connections.h"
+#include "response.h"
+#include "connections.h"
+#include "log.h"
+
+#include "plugin.h"
+#ifdef HAVE_FORK
+/* no need for waitpid if we don't have fork */
+#include <sys/wait.h>
+#endif
+typedef struct {
+ buffer *path_rrdtool_bin;
+ buffer *path_rrd;
+
+ double requests, *requests_ptr;
+ double bytes_written, *bytes_written_ptr;
+ double bytes_read, *bytes_read_ptr;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *cmd;
+ buffer *resp;
+
+ int read_fd, write_fd;
+ pid_t rrdtool_pid;
+
+ int rrdtool_running;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_rrd_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->resp = buffer_init();
+ p->cmd = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_rrd_free) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->path_rrdtool_bin);
+ buffer_free(s->path_rrd);
+
+ free(s);
+ }
+ }
+ buffer_free(p->cmd);
+ buffer_free(p->resp);
+
+ free(p->config_storage);
+
+ if (p->rrdtool_pid) {
+ int status;
+ close(p->read_fd);
+ close(p->write_fd);
+#ifdef HAVE_FORK
+ /* collect status */
+ waitpid(p->rrdtool_pid, &status, 0);
+#endif
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+int mod_rrd_create_pipe(server *srv, plugin_data *p) {
+ pid_t pid;
+
+ int to_rrdtool_fds[2];
+ int from_rrdtool_fds[2];
+#ifdef HAVE_FORK
+ if (pipe(to_rrdtool_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ if (pipe(from_rrdtool_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0: {
+ /* child */
+ char **args;
+ int argc;
+ int i = 0;
+ char *dash = "-";
+
+ /* move stdout to from_rrdtool_fd[1] */
+ close(STDOUT_FILENO);
+ dup2(from_rrdtool_fds[1], STDOUT_FILENO);
+ close(from_rrdtool_fds[1]);
+ /* not needed */
+ close(from_rrdtool_fds[0]);
+
+ /* move the stdin to to_rrdtool_fd[0] */
+ close(STDIN_FILENO);
+ dup2(to_rrdtool_fds[0], STDIN_FILENO);
+ close(to_rrdtool_fds[0]);
+ /* not needed */
+ close(to_rrdtool_fds[1]);
+
+ /* set up args */
+ argc = 3;
+ args = malloc(sizeof(*args) * argc);
+ i = 0;
+
+ args[i++] = p->conf.path_rrdtool_bin->ptr;
+ args[i++] = dash;
+ args[i++] = NULL;
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ if (i != srv->log_error_fd) close(i);
+ }
+
+ /* exec the cgi */
+ execv(args[0], args);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss", "spawing rrdtool failed: ", strerror(errno), args[0]);
+
+ /* */
+ SEGFAULT();
+ break;
+ }
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno));
+ break;
+ default: {
+ /* father */
+
+ close(from_rrdtool_fds[1]);
+ close(to_rrdtool_fds[0]);
+
+ /* register PID and wait for them asyncronously */
+ p->write_fd = to_rrdtool_fds[1];
+ p->read_fd = from_rrdtool_fds[0];
+ p->rrdtool_pid = pid;
+
+ break;
+ }
+ }
+
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, plugin_config *s) {
+ struct stat st;
+
+ /* check if DB already exists */
+ if (0 == stat(s->path_rrd->ptr, &st)) {
+ /* check if it is plain file */
+ if (!S_ISREG(st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "not a regular file:", s->path_rrd);
+ return HANDLER_ERROR;
+ }
+ } else {
+ int r ;
+ /* create a new one */
+
+ BUFFER_COPY_STRING_CONST(p->cmd, "create ");
+ buffer_append_string_buffer(p->cmd, s->path_rrd);
+ buffer_append_string(p->cmd, " --step 60 ");
+ buffer_append_string(p->cmd, "DS:InOctets:ABSOLUTE:600:U:U ");
+ buffer_append_string(p->cmd, "DS:OutOctets:ABSOLUTE:600:U:U ");
+ buffer_append_string(p->cmd, "DS:Requests:ABSOLUTE:600:U:U ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:1:600 ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:6:700 ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:24:775 ");
+ buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:288:797 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:1:600 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:6:700 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:24:775 ");
+ buffer_append_string(p->cmd, "RRA:MAX:0.5:288:797 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:1:600 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:6:700 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:24:775 ");
+ buffer_append_string(p->cmd, "RRA:MIN:0.5:288:797\n");
+
+ if (-1 == (r = write(p->write_fd, p->cmd->ptr, p->cmd->used - 1))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-write: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ buffer_prepare_copy(p->resp, 4096);
+ if (-1 == (r = read(p->read_fd, p->resp->ptr, p->resp->size))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-read: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ p->resp->used = r;
+
+ if (p->resp->ptr[0] != 'O' ||
+ p->resp->ptr[1] != 'K') {
+ log_error_write(srv, __FILE__, __LINE__, "sbb",
+ "rrdtool-response:", p->cmd, p->resp);
+
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_rrd_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("rrdtool.db-name"))) {
+ PATCH(path_rrd);
+ /* get pointers to double values */
+
+ p->conf.bytes_written_ptr = &(s->bytes_written);
+ p->conf.bytes_read_ptr = &(s->bytes_read);
+ p->conf.requests_ptr = &(s->requests);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_rrd_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(path_rrdtool_bin);
+ PATCH(path_rrd);
+
+ p->conf.bytes_written_ptr = &(s->bytes_written);
+ p->conf.bytes_read_ptr = &(s->bytes_read);
+ p->conf.requests_ptr = &(s->requests);
+
+ return 0;
+}
+#undef PATCH
+
+SETDEFAULTS_FUNC(mod_rrd_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "rrdtool.binary", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER },
+ { "rrdtool.db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->path_rrdtool_bin = buffer_init();
+ s->path_rrd = buffer_init();
+ s->requests = 0;
+ s->bytes_written = 0;
+ s->bytes_read = 0;
+
+ cv[0].destination = s->path_rrdtool_bin;
+ cv[1].destination = s->path_rrd;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (i > 0 && !buffer_is_empty(s->path_rrdtool_bin)) {
+ /* path_rrdtool_bin is a global option */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "rrdtool.binary can only be set as a global option.");
+
+ return HANDLER_ERROR;
+ }
+
+ }
+
+ p->conf.path_rrdtool_bin = p->config_storage[0]->path_rrdtool_bin;
+ p->rrdtool_running = 0;
+
+ /* check for dir */
+
+ if (buffer_is_empty(p->conf.path_rrdtool_bin)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "rrdtool.binary has to be set");
+ return HANDLER_ERROR;
+ }
+
+ /* open the pipe to rrdtool */
+ if (mod_rrd_create_pipe(srv, p)) {
+ return HANDLER_ERROR;
+ }
+
+ p->rrdtool_running = 1;
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(mod_rrd_trigger) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ if (!p->rrdtool_running) return HANDLER_GO_ON;
+ if ((srv->cur_ts % 60) != 0) return HANDLER_GO_ON;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+ int r;
+
+ if (buffer_is_empty(s->path_rrd)) continue;
+
+ /* write the data down every minute */
+
+ if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s)) return HANDLER_ERROR;
+
+ BUFFER_COPY_STRING_CONST(p->cmd, "update ");
+ buffer_append_string_buffer(p->cmd, s->path_rrd);
+ BUFFER_APPEND_STRING_CONST(p->cmd, " N:");
+ buffer_append_off_t(p->cmd, s->bytes_read);
+ BUFFER_APPEND_STRING_CONST(p->cmd, ":");
+ buffer_append_off_t(p->cmd, s->bytes_written);
+ BUFFER_APPEND_STRING_CONST(p->cmd, ":");
+ buffer_append_long(p->cmd, s->requests);
+ BUFFER_APPEND_STRING_CONST(p->cmd, "\n");
+
+ if (-1 == (r = write(p->write_fd, p->cmd->ptr, p->cmd->used - 1))) {
+ p->rrdtool_running = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-write: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ buffer_prepare_copy(p->resp, 4096);
+ if (-1 == (r = read(p->read_fd, p->resp->ptr, p->resp->size))) {
+ p->rrdtool_running = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "rrdtool-read: failed", strerror(errno));
+
+ return HANDLER_ERROR;
+ }
+
+ p->resp->used = r;
+
+ if (p->resp->ptr[0] != 'O' ||
+ p->resp->ptr[1] != 'K') {
+ p->rrdtool_running = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbb",
+ "rrdtool-response:", p->cmd, p->resp);
+
+ return HANDLER_ERROR;
+ }
+ s->requests = 0;
+ s->bytes_written = 0;
+ s->bytes_read = 0;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+REQUESTDONE_FUNC(mod_rrd_account) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ mod_rrd_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_rrd_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ *(p->conf.requests_ptr) += 1;
+ *(p->conf.bytes_written_ptr) += con->bytes_written;
+ *(p->conf.bytes_read_ptr) += con->bytes_read;
+
+ return HANDLER_GO_ON;
+}
+
+int mod_rrdtool_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("rrd");
+
+ p->init = mod_rrd_init;
+ p->cleanup = mod_rrd_free;
+ p->set_defaults= mod_rrd_set_defaults;
+
+ p->handle_trigger = mod_rrd_trigger;
+ p->handle_request_done = mod_rrd_account;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_secure_download.c b/src/mod_secure_download.c
new file mode 100644
index 00000000..eecc230a
--- /dev/null
+++ b/src/mod_secure_download.c
@@ -0,0 +1,323 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5_global.h"
+# include "md5.h"
+#endif
+
+#define HASHLEN 16
+typedef unsigned char HASH[HASHLEN];
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+#ifdef USE_OPENSSL
+#define IN const
+#else
+#define IN
+#endif
+#define OUT
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *doc_root;
+ buffer *secret;
+ buffer *uri_prefix;
+
+ time_t timeout;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *md5;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_secdownload_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->md5 = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_secdownload_free) {
+ plugin_data *p = p_d;
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->secret);
+ buffer_free(s->doc_root);
+ buffer_free(s->uri_prefix);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->md5);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_secdownload_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "secdownload.secret", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "secdownload.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "secdownload.uri-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "secdownload.timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->secret = buffer_init();
+ s->doc_root = buffer_init();
+ s->uri_prefix = buffer_init();
+ s->timeout = 0;
+
+ /* set global default */
+ if (i == 0) {
+ s->timeout = 60;
+ buffer_copy_string(s->uri_prefix, "/");
+ }
+
+ cv[0].destination = s->secret;
+ cv[1].destination = s->doc_root;
+ cv[2].destination = s->uri_prefix;
+ cv[3].destination = &(s->timeout);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/**
+ * checks if the supplied string is a MD5 string
+ *
+ * @param str a possible MD5 string
+ * @return if the supplied string is a valid MD5 string 1 is returned otherwise 0
+ */
+
+int is_hex_len(const char *str, size_t len) {
+ size_t i;
+
+ if (NULL == str) return 0;
+
+ for (i = 0; i < len && *str; i++, str++) {
+ /* illegal characters */
+ if (!((*str >= '0' && *str <= '9') ||
+ (*str >= 'a' && *str <= 'f') ||
+ (*str >= 'A' && *str <= 'F'))
+ ) {
+ return 0;
+ }
+ }
+
+ return i == len;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_secdownload_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.secret"))) {
+ PATCH(secret);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.document-root"))) {
+ PATCH(doc_root);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.uri-prefix"))) {
+ PATCH(uri_prefix);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.timeout"))) {
+ PATCH(timeout);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_secdownload_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(secret);
+ PATCH(doc_root);
+ PATCH(uri_prefix);
+ PATCH(timeout);
+
+ return 0;
+}
+#undef PATCH
+
+
+URIHANDLER_FUNC(mod_secdownload_uri_handler) {
+ plugin_data *p = p_d;
+ MD5_CTX Md5Ctx;
+ HASH HA1;
+ const char *rel_uri, *ts_str, *md5_str;
+ time_t ts = 0;
+ size_t i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_secdownload_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_secdownload_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (buffer_is_empty(p->conf.secret)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "secdownload.secret has to be set");
+ return HANDLER_ERROR;
+ }
+
+ if (buffer_is_empty(p->conf.doc_root)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "secdownload.document-root has to be set");
+ return HANDLER_ERROR;
+ }
+
+ /*
+ * /<uri-prefix>[a-f0-9]{32}/[a-f0-9]{8}/<rel-path>
+ */
+
+ if (0 != strncmp(con->uri.path->ptr, p->conf.uri_prefix->ptr, p->conf.uri_prefix->used - 1)) return HANDLER_GO_ON;
+
+ md5_str = con->uri.path->ptr + p->conf.uri_prefix->used - 1;
+
+ if (!is_hex_len(md5_str, 32)) return HANDLER_GO_ON;
+ if (*(md5_str + 32) != '/') return HANDLER_GO_ON;
+
+ ts_str = md5_str + 32 + 1;
+
+ if (!is_hex_len(ts_str, 8)) return HANDLER_GO_ON;
+ if (*(ts_str + 8) != '/') return HANDLER_GO_ON;
+
+ for (i = 0; i < 8; i++) {
+ ts = (ts << 4) + hex2int(*(ts_str + i));
+ }
+
+ /* timed-out */
+ if (srv->cur_ts - ts > p->conf.timeout ||
+ srv->cur_ts - ts < -p->conf.timeout) {
+ con->http_status = 408;
+
+ return HANDLER_FINISHED;
+ }
+
+ rel_uri = ts_str + 8;
+
+ /* checking MD5
+ *
+ * <secret><rel-path><timestamp-hex>
+ */
+
+ buffer_copy_string_buffer(p->md5, p->conf.secret);
+ buffer_append_string(p->md5, rel_uri);
+ buffer_append_string_len(p->md5, ts_str, 8);
+
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)p->md5->ptr, p->md5->used - 1);
+ MD5_Final(HA1, &Md5Ctx);
+
+ buffer_copy_string_hex(p->md5, (char *)HA1, 16);
+
+ if (0 != strncmp(md5_str, p->md5->ptr, 32)) {
+ con->http_status = 403;
+
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "md5 invalid:",
+ md5_str, p->md5->ptr);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* starting with the last / we should have relative-path to the docroot
+ */
+
+ buffer_copy_string_buffer(con->physical.path, p->conf.doc_root);
+ buffer_append_string(con->physical.path, rel_uri);
+
+ return HANDLER_COMEBACK;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_secdownload_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("secdownload");
+
+ p->init = mod_secdownload_init;
+ p->handle_uri_clean = mod_secdownload_uri_handler;
+ p->set_defaults = mod_secdownload_set_defaults;
+ p->cleanup = mod_secdownload_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_setenv.c b/src/mod_setenv.c
new file mode 100644
index 00000000..8cfca42e
--- /dev/null
+++ b/src/mod_setenv.c
@@ -0,0 +1,215 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+#include "response.h"
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *request_header;
+ array *response_header;
+
+ array *environment;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_setenv_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_setenv_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->request_header);
+ array_free(s->response_header);
+ array_free(s->environment);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_setenv_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "setenv.add-request-header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "setenv.add-response-header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "setenv.add-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->request_header = array_init();
+ s->response_header = array_init();
+ s->environment = array_init();
+
+ cv[0].destination = s->request_header;
+ cv[1].destination = s->response_header;
+ cv[2].destination = s->environment;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_setenv_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-request-header"))) {
+ PATCH(request_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-response-header"))) {
+ PATCH(response_header);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.environment"))) {
+ PATCH(environment);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_setenv_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(request_header);
+ PATCH(response_header);
+ PATCH(environment);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_setenv_uri_handler) {
+ plugin_data *p = p_d;
+ size_t k, i;
+
+ mod_setenv_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_setenv_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ for (k = 0; k < p->conf.request_header->used; k++) {
+ data_string *ds = (data_string *)p->conf.request_header->data[k];
+ data_string *ds_dst;
+
+ if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+ ds_dst = data_string_init();
+ }
+
+ buffer_copy_string_buffer(ds_dst->key, ds->key);
+ buffer_copy_string_buffer(ds_dst->value, ds->value);
+
+ array_insert_unique(con->request.headers, (data_unset *)ds_dst);
+ }
+
+ for (k = 0; k < p->conf.environment->used; k++) {
+ data_string *ds = (data_string *)p->conf.environment->data[k];
+ data_string *ds_dst;
+
+ if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) {
+ ds_dst = data_string_init();
+ }
+
+ buffer_copy_string_buffer(ds_dst->key, ds->key);
+ buffer_copy_string_buffer(ds_dst->value, ds->value);
+
+ array_insert_unique(con->environment, (data_unset *)ds_dst);
+ }
+
+ for (k = 0; k < p->conf.response_header->used; k++) {
+ data_string *ds = (data_string *)p->conf.response_header->data[k];
+
+ response_header_insert(srv, con, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_setenv_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("setenv");
+
+ p->init = mod_setenv_init;
+ p->handle_uri_clean = mod_setenv_uri_handler;
+ p->set_defaults = mod_setenv_set_defaults;
+ p->cleanup = mod_setenv_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_simple_vhost.c b/src/mod_simple_vhost.c
new file mode 100644
index 00000000..9fcef860
--- /dev/null
+++ b/src/mod_simple_vhost.c
@@ -0,0 +1,284 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+#include "file_cache.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+typedef struct {
+ buffer *server_root;
+ buffer *default_host;
+ buffer *document_root;
+
+ buffer *docroot_cache_key;
+ buffer *docroot_cache_value;
+ buffer *docroot_cache_servername;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *doc_root;
+
+ plugin_config **config_storage;
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_simple_vhost_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->doc_root = buffer_init();
+
+ return p;
+}
+
+FREE_FUNC(mod_simple_vhost_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->document_root);
+ buffer_free(s->default_host);
+ buffer_free(s->server_root);
+
+ buffer_free(s->docroot_cache_key);
+ buffer_free(s->docroot_cache_value);
+ buffer_free(s->docroot_cache_servername);
+
+ free(s);
+ }
+
+ free(p->config_storage);
+ }
+
+ buffer_free(p->doc_root);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_simple_vhost_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "simple-vhost.server-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "simple-vhost.default-host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "simple-vhost.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+
+ s->server_root = buffer_init();
+ s->default_host = buffer_init();
+ s->document_root = buffer_init();
+
+ s->docroot_cache_key = buffer_init();
+ s->docroot_cache_value = buffer_init();
+ s->docroot_cache_servername = buffer_init();
+
+ cv[0].destination = s->server_root;
+ cv[1].destination = s->default_host;
+ cv[2].destination = s->document_root;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+static int build_doc_root(server *srv, connection *con, plugin_data *p, buffer *out, buffer *host) {
+ buffer_prepare_copy(out, 128);
+
+ if (p->conf.server_root->used) {
+ buffer_copy_string_buffer(out, p->conf.server_root);
+
+ if (host->used) {
+ /* a hostname has to start with a alpha-numerical character
+ * and must not contain a slash "/"
+ */
+ char *dp;
+
+ BUFFER_APPEND_SLASH(out);
+
+ if (NULL == (dp = strchr(host->ptr, ':'))) {
+ buffer_append_string_buffer(out, host);
+ } else {
+ buffer_append_string_len(out, host->ptr, dp - host->ptr);
+ }
+ }
+ BUFFER_APPEND_SLASH(out);
+
+ if (p->conf.document_root->used > 2 && p->conf.document_root->ptr[0] == '/') {
+ buffer_append_string_len(out, p->conf.document_root->ptr + 1, p->conf.document_root->used - 2);
+ } else {
+ buffer_append_string_buffer(out, p->conf.document_root);
+ BUFFER_APPEND_SLASH(out);
+ }
+ } else {
+ buffer_copy_string_buffer(out, con->conf.document_root);
+ BUFFER_APPEND_SLASH(out);
+ }
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, out, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), out);
+ return -1;
+ }
+
+ if (!S_ISDIR(con->fce->st.st_mode)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_simple_vhost_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.server-root"))) {
+ PATCH(server_root);
+ PATCH(docroot_cache_key);
+ PATCH(docroot_cache_value);
+ PATCH(docroot_cache_servername);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.default-host"))) {
+ PATCH(default_host);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.document-root"))) {
+ PATCH(document_root);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_simple_vhost_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(server_root);
+ PATCH(default_host);
+ PATCH(document_root);
+
+ PATCH(docroot_cache_key);
+ PATCH(docroot_cache_value);
+ PATCH(docroot_cache_servername);
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_simple_vhost_docroot(server *srv, connection *con, void *p_data) {
+ plugin_data *p = p_data;
+ size_t i;
+
+ /*
+ * cache the last successfull translation from hostname (authority) to docroot
+ * - this saves us a stat() call
+ *
+ */
+
+ mod_simple_vhost_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_simple_vhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (p->conf.docroot_cache_key->used &&
+ con->uri.authority->used &&
+ buffer_is_equal(p->conf.docroot_cache_key, con->uri.authority)) {
+ /* cache hit */
+ buffer_copy_string_buffer(con->physical.doc_root, p->conf.docroot_cache_value);
+ buffer_copy_string_buffer(con->server_name, p->conf.docroot_cache_servername);
+ } else {
+ /* build document-root */
+ if ((con->uri.authority->used == 0) ||
+ build_doc_root(srv, con, p, p->doc_root, con->uri.authority)) {
+ /* not found, fallback the default-host */
+ if (build_doc_root(srv, con, p,
+ p->doc_root,
+ p->conf.default_host)) {
+ return HANDLER_GO_ON;
+ } else {
+ buffer_copy_string_buffer(con->server_name, p->conf.default_host);
+ }
+ } else {
+ buffer_copy_string_buffer(con->server_name, con->uri.authority);
+ }
+
+ /* copy to cache */
+ buffer_copy_string_buffer(p->conf.docroot_cache_key, con->uri.authority);
+ buffer_copy_string_buffer(p->conf.docroot_cache_value, p->doc_root);
+ buffer_copy_string_buffer(p->conf.docroot_cache_servername, con->server_name);
+
+ buffer_copy_string_buffer(con->physical.doc_root, p->doc_root);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_simple_vhost_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("simple_vhost");
+
+ p->init = mod_simple_vhost_init;
+ p->set_defaults = mod_simple_vhost_set_defaults;
+ p->handle_docroot = mod_simple_vhost_docroot;
+ p->cleanup = mod_simple_vhost_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_skeleton.c b/src/mod_skeleton.c
new file mode 100644
index 00000000..b5dab829
--- /dev/null
+++ b/src/mod_skeleton.c
@@ -0,0 +1,220 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+/**
+ * this is a skeleton for a lighttpd plugin
+ *
+ * just replaces every occurance of 'skeleton' by your plugin name
+ *
+ * e.g. in vim:
+ *
+ * :%s/skeleton/myhandler/
+ *
+ */
+
+
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *match;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *match_buf;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+typedef struct {
+ size_t foo;
+} handler_ctx;
+
+static handler_ctx * handler_ctx_init() {
+ handler_ctx * hctx;
+
+ hctx = calloc(1, sizeof(*hctx));
+
+ return hctx;
+}
+
+static void handler_ctx_free(handler_ctx *hctx) {
+
+ free(hctx);
+}
+
+/* init the plugin data */
+INIT_FUNC(mod_skeleton_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->match_buf = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_skeleton_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->match);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->match_buf);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_skeleton_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "skeleton.array", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->match = array_init();
+
+ cv[0].destination = s->match;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_skeleton_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("skeleton.array"))) {
+ PATCH(match);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_skeleton_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(match);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_skeleton_uri_handler) {
+ plugin_data *p = p_d;
+ int s_len;
+ size_t k, i;
+
+ UNUSED(srv);
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_skeleton_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_skeleton_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ s_len = con->uri.path->used - 1;
+
+ for (k = 0; k < p->conf.match->used; k++) {
+ data_string *ds = (data_string *)p->conf.match->data[k];
+ int ct_len = ds->value->used - 1;
+
+ if (ct_len > s_len) continue;
+ if (ds->value->used == 0) continue;
+
+ if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
+ con->http_status = 403;
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_skeleton_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("skeleton");
+
+ p->init = mod_skeleton_init;
+ p->handle_uri_clean = mod_skeleton_uri_handler;
+ p->set_defaults = mod_skeleton_set_defaults;
+ p->cleanup = mod_skeleton_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_ssi.c b/src/mod_ssi.c
new file mode 100644
index 00000000..038c08e6
--- /dev/null
+++ b/src/mod_ssi.c
@@ -0,0 +1,1073 @@
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+#include "stream.h"
+
+#include "response.h"
+
+#include "mod_ssi.h"
+
+#include "inet_ntop_cache.h"
+
+#include "sys-socket.h"
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_FORK
+#include <sys/wait.h>
+#endif
+/* init the plugin data */
+INIT_FUNC(mod_ssi_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->timefmt = buffer_init();
+ p->stat_fn = buffer_init();
+
+ p->ssi_vars = array_init();
+ p->ssi_cgi_env = array_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_ssi_free) {
+ plugin_data *p = p_d;
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->ssi_extension);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ array_free(p->ssi_vars);
+ array_free(p->ssi_cgi_env);
+#ifdef HAVE_PCRE_H
+ pcre_free(p->ssi_regex);
+#endif
+ buffer_free(p->timefmt);
+ buffer_free(p->stat_fn);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+#ifdef HAVE_PCRE_H
+ const char *errptr;
+ int erroff;
+#endif
+
+ config_values_t cv[] = {
+ { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->ssi_extension = array_init();
+
+ cv[0].destination = s->ssi_extension;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+#ifdef HAVE_PCRE_H
+ /* allow 2 params */
+ if (NULL == (p->ssi_regex = pcre_compile("<!--#([a-z]+)\\s+(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?-->", 0, &errptr, &erroff, NULL))) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "ssi: pcre ",
+ erroff, errptr);
+ return HANDLER_ERROR;
+ }
+#else
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "mod_ssi: pcre support is missing, please recompile with pcre support or remove mod_ssi from the list of modules");
+ return HANDLER_ERROR;
+#endif
+
+ return HANDLER_GO_ON;
+}
+
+int ssi_env_add(array *env, const char *key, const char *val) {
+ data_string *ds;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+ buffer_copy_string(ds->key, key);
+ buffer_copy_string(ds->value, val);
+
+ array_insert_unique(env, (data_unset *)ds);
+
+ return 0;
+}
+
+/**
+ *
+ * the next two functions are take from fcgi.c
+ *
+ */
+
+static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
+ size_t i;
+
+ for (i = 0; i < con->request.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->request.headers->data[i];
+
+ if (ds->value->used && ds->key->used) {
+ size_t j;
+ buffer_reset(srv->tmp_buf);
+
+ /* don't forward the Authorization: Header */
+ if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) {
+ continue;
+ }
+
+ if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
+ buffer_copy_string(srv->tmp_buf, "HTTP_");
+ srv->tmp_buf->used--;
+ }
+
+ buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
+ for (j = 0; j < ds->key->used - 1; j++) {
+ srv->tmp_buf->ptr[srv->tmp_buf->used++] =
+ isalpha((unsigned char)ds->key->ptr[j]) ?
+ toupper((unsigned char)ds->key->ptr[j]) : '_';
+ }
+ srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0';
+
+ ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
+ }
+ }
+
+ return 0;
+}
+
+static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
+ char buf[32];
+
+ server_socket *srv_sock = con->srv_socket;
+
+#ifdef HAVE_IPV6
+ char b2[INET6_ADDRSTRLEN + 1];
+#endif
+
+#define CONST_STRING(x) \
+ x
+
+ array_reset(p->ssi_cgi_env);
+
+#ifdef PACKAGE_NAME
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION);
+#else
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE"/"VERSION);
+#endif
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"),
+#ifdef HAVE_IPV6
+ inet_ntop(srv_sock->addr.plain.sa_family,
+ srv_sock->addr.plain.sa_family == AF_INET6 ?
+ (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
+ (const void *) &(srv_sock->addr.ipv4.sin_addr),
+ b2, sizeof(b2)-1)
+#else
+ inet_ntoa(srv_sock->addr.ipv4.sin_addr)
+#endif
+ );
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
+
+ ltostr(buf,
+#ifdef HAVE_IPV6
+ ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
+#else
+ ntohs(srv_sock->addr.ipv4.sin_port)
+#endif
+ );
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf);
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"),
+ inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
+
+ if (con->authed_user->used) {
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_USER"),
+ con->authed_user->ptr);
+ }
+
+ if (con->request.content_length > 0) {
+ /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
+
+ /* request.content_length < SSIZE_MAX, see request.c */
+ ltostr(buf, con->request.content_length);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf);
+ }
+
+ /*
+ * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
+ * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
+ * (6.1.14, 6.1.6, 6.1.7)
+ */
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), "");
+
+ /*
+ * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
+ * http://www.php.net/manual/en/reserved.variables.php
+ * treatment of PATH_TRANSLATED is different from the one of CGI specs.
+ * TODO: this code should be checked against cgi.fix_pathinfo php
+ * parameter.
+ */
+
+ if (con->request.pathinfo->used) {
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr);
+ }
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.doc_root->ptr);
+
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr);
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : "");
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200");
+ ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
+
+ ssi_env_add_request_headers(srv, con, p);
+
+ return 0;
+}
+
+static int process_ssi_stmt(server *srv, connection *con, plugin_data *p,
+ const char **l, size_t n) {
+ size_t i, ssicmd = 0;
+ char buf[255];
+ buffer *b = NULL;
+
+ struct {
+ const char *var;
+ enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
+ SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
+ SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
+ } ssicmds[] = {
+ { "echo", SSI_ECHO },
+ { "include", SSI_INCLUDE },
+ { "flastmod", SSI_FLASTMOD },
+ { "fsize", SSI_FSIZE },
+ { "config", SSI_CONFIG },
+ { "printenv", SSI_PRINTENV },
+ { "set", SSI_SET },
+ { "if", SSI_IF },
+ { "elif", SSI_ELIF },
+ { "endif", SSI_ENDIF },
+ { "else", SSI_ELSE },
+ { "exec", SSI_EXEC },
+
+ { NULL, SSI_UNSET }
+ };
+
+ for (i = 0; ssicmds[i].var; i++) {
+ if (0 == strcmp(l[1], ssicmds[i].var)) {
+ ssicmd = ssicmds[i].type;
+ break;
+ }
+ }
+
+ switch(ssicmd) {
+ case SSI_ECHO: {
+ /* echo */
+ int var = 0, enc = 0;
+ const char *var_val = NULL;
+
+ struct {
+ const char *var;
+ enum { SSI_ECHO_UNSET, SSI_ECHO_DATE_GMT, SSI_ECHO_DATE_LOCAL, SSI_ECHO_DOCUMENT_NAME, SSI_ECHO_DOCUMENT_URI,
+ SSI_ECHO_LAST_MODIFIED, SSI_ECHO_USER_NAME } type;
+ } echovars[] = {
+ { "DATE_GMT", SSI_ECHO_DATE_GMT },
+ { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
+ { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
+ { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
+ { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
+ { "USER_NAME", SSI_ECHO_USER_NAME },
+
+ { NULL, SSI_ECHO_UNSET }
+ };
+
+ struct {
+ const char *var;
+ enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
+ } encvars[] = {
+ { "url", SSI_ENC_URL },
+ { "none", SSI_ENC_NONE },
+ { "entity", SSI_ENC_ENTITY },
+
+ { NULL, SSI_ENC_UNSET }
+ };
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "var")) {
+ int j;
+
+ var_val = l[i+1];
+
+ for (j = 0; echovars[j].var; j++) {
+ if (0 == strcmp(l[i+1], echovars[j].var)) {
+ var = echovars[j].type;
+ break;
+ }
+ }
+ } else if (0 == strcmp(l[i], "encoding")) {
+ int j;
+
+ for (j = 0; encvars[j].var; j++) {
+ if (0 == strcmp(l[i+1], encvars[j].var)) {
+ enc = encvars[j].type;
+ break;
+ }
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (p->if_is_false) break;
+
+ if (!var_val) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "var is missing");
+ break;
+ }
+
+ switch(var) {
+ case SSI_ECHO_USER_NAME: {
+ struct passwd *pw;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+#ifdef HAVE_PWD_H
+ if (NULL == (pw = getpwuid(con->fce->st.st_uid))) {
+ buffer_copy_long(b, con->fce->st.st_uid);
+ } else {
+ buffer_copy_string(b, pw->pw_name);
+ }
+#else
+ buffer_copy_long(b, con->fce->st.st_uid);
+#endif
+ break;
+ }
+ case SSI_ECHO_LAST_MODIFIED: {
+ time_t t = con->fce->st.st_mtime;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ }
+ case SSI_ECHO_DATE_LOCAL: {
+ time_t t = time(NULL);
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ }
+ case SSI_ECHO_DATE_GMT: {
+ time_t t = time(NULL);
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ }
+ case SSI_ECHO_DOCUMENT_NAME: {
+ char *sl;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
+ buffer_copy_string_buffer(b, con->physical.path);
+ } else {
+ buffer_copy_string(b, sl + 1);
+ }
+ break;
+ }
+ case SSI_ECHO_DOCUMENT_URI: {
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_string_buffer(b, con->uri.path);
+ break;
+ }
+ default: {
+ data_string *ds;
+ /* check if it is a cgi-var */
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val))) {
+ buffer_copy_string_buffer(b, ds->value);
+ } else {
+ buffer_copy_string(b, "(none)");
+ }
+
+ break;
+ }
+ }
+ break;
+ }
+ case SSI_INCLUDE:
+ case SSI_FLASTMOD:
+ case SSI_FSIZE: {
+ const char * file_path = NULL, *virt_path = NULL;
+ struct stat st;
+ char *sl;
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "file")) {
+ file_path = l[i+1];
+ } else if (0 == strcmp(l[i], "virtual")) {
+ virt_path = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (!file_path && !virt_path) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "file or virtual are missing");
+ break;
+ }
+
+ if (file_path && virt_path) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "only one of file and virtual is allowed here");
+ break;
+ }
+
+
+ if (p->if_is_false) break;
+
+ if (file_path) {
+ /* current doc-root */
+ if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
+ buffer_copy_string(p->stat_fn, "/");
+ } else {
+ buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
+ }
+
+ /* fn */
+ if (NULL == (sl = strrchr(file_path, '/'))) {
+ buffer_append_string(p->stat_fn, file_path);
+ } else {
+ buffer_append_string(p->stat_fn, sl + 1);
+ }
+ } else {
+ /* virtual */
+
+ if (virt_path[0] == '/') {
+ buffer_copy_string(p->stat_fn, virt_path);
+ } else {
+ /* there is always a / */
+ sl = strrchr(con->uri.path->ptr, '/');
+
+ buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
+ buffer_append_string(p->stat_fn, virt_path);
+ }
+
+ buffer_urldecode(p->stat_fn);
+ buffer_path_simplify(&(srv->dot_stack), srv->tmp_buf, p->stat_fn);
+
+ /* we have an uri */
+
+ buffer_copy_string_buffer(p->stat_fn, con->physical.doc_root);
+ buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
+ }
+
+ if (0 == stat(p->stat_fn->ptr, &st)) {
+ time_t t = st.st_mtime;
+
+ switch (ssicmd) {
+ case SSI_FSIZE:
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (p->sizefmt) {
+ int j = 0;
+ const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
+
+ off_t s = st.st_size;
+
+ for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
+
+ buffer_copy_off_t(b, s);
+ buffer_append_string(b, abr[j]);
+ } else {
+ buffer_copy_off_t(b, st.st_size);
+ }
+ break;
+ case SSI_FLASTMOD:
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
+ buffer_copy_string(b, "(none)");
+ } else {
+ buffer_copy_string(b, buf);
+ }
+ break;
+ case SSI_INCLUDE:
+ chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size);
+ break;
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "ssi: stating failed ",
+ p->stat_fn, strerror(errno));
+ }
+ break;
+ }
+ case SSI_SET: {
+ const char *key = NULL, *val = NULL;
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "var")) {
+ key = l[i+1];
+ } else if (0 == strcmp(l[i], "value")) {
+ val = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (p->if_is_false) break;
+
+ if (key && val) {
+ data_string *ds;
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+ buffer_copy_string(ds->key, key);
+ buffer_copy_string(ds->value, val);
+
+ array_insert_unique(p->ssi_vars, (data_unset *)ds);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: var and value have to be set in",
+ l[0], l[1]);
+ }
+ break;
+ }
+ case SSI_CONFIG:
+ if (p->if_is_false) break;
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "timefmt")) {
+ buffer_copy_string(p->timefmt, l[i+1]);
+ } else if (0 == strcmp(l[i], "sizefmt")) {
+ if (0 == strcmp(l[i+1], "abbrev")) {
+ p->sizefmt = 1;
+ } else if (0 == strcmp(l[i+1], "abbrev")) {
+ p->sizefmt = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sssss",
+ "ssi: unknow value for attribute '",
+ l[i],
+ "' for ",
+ l[1], l[i+1]);
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+ break;
+ case SSI_PRINTENV:
+ if (p->if_is_false) break;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_string(b, "<pre>");
+ for (i = 0; i < p->ssi_vars->used; i++) {
+ data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
+
+ buffer_append_string_buffer(b, ds->key);
+ buffer_append_string(b, ": ");
+ buffer_append_string_buffer(b, ds->value);
+ buffer_append_string(b, "<br />");
+
+ }
+ buffer_append_string(b, "</pre>");
+
+ break;
+ case SSI_EXEC: {
+ const char *cmd = NULL;
+ pid_t pid;
+ int from_exec_fds[2];
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "cmd")) {
+ cmd = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (p->if_is_false) break;
+
+ /* create a return pipe and send output to the html-page
+ *
+ * as exec is assumed evil it is implemented synchronously
+ */
+
+ if (!cmd) break;
+#ifdef HAVE_FORK
+ if (pipe(from_exec_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "pipe failed: ", strerror(errno));
+ return -1;
+ }
+
+ /* fork, execve */
+ switch (pid = fork()) {
+ case 0: {
+ /* move stdout to from_rrdtool_fd[1] */
+ close(STDOUT_FILENO);
+ dup2(from_exec_fds[1], STDOUT_FILENO);
+ close(from_exec_fds[1]);
+ /* not needed */
+ close(from_exec_fds[0]);
+
+ /* close stdin */
+ close(STDIN_FILENO);
+
+ execl("/bin/sh", "sh", "-c", cmd, NULL);
+
+ log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
+
+ /* */
+ SEGFAULT();
+ break;
+ }
+ case -1:
+ /* error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
+ break;
+ default: {
+ /* father */
+ int status;
+ ssize_t r;
+
+ close(from_exec_fds[1]);
+
+ /* wait for the client to end */
+ if (-1 == waitpid(pid, &status, 0)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
+ } else if (WIFEXITED(status)) {
+
+ /* read everything from client and paste it into the output */
+
+ for (b = chunkqueue_get_append_buffer(con->write_queue), buffer_prepare_copy(b, 4096);
+ (r = read(from_exec_fds[0], b->ptr, b->size - 1)) > 0;
+ b = chunkqueue_get_append_buffer(con->write_queue), buffer_prepare_copy(b, 4096)) {
+
+ b->used = r;
+ b->ptr[b->used++] = '\0';
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
+ }
+ close(from_exec_fds[0]);
+
+ break;
+ }
+ }
+#else
+
+ return -1;
+#endif
+
+ break;
+ }
+ case SSI_IF: {
+ const char *expr = NULL;
+
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "expr")) {
+ expr = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (!expr) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "expr missing");
+ break;
+ }
+
+ if ((!p->if_is_false) &&
+ ((p->if_is_false_level == 0) ||
+ (p->if_level < p->if_is_false_level))) {
+ switch (ssi_eval_expr(srv, con, p, expr)) {
+ case -1:
+ case 0:
+ p->if_is_false = 1;
+ p->if_is_false_level = p->if_level;
+ break;
+ case 1:
+ p->if_is_false = 0;
+ break;
+ }
+ }
+
+ p->if_level++;
+
+ break;
+ }
+ case SSI_ELSE:
+ p->if_level--;
+
+ if (p->if_is_false) {
+ if ((p->if_level == p->if_is_false_level) &&
+ (p->if_is_false_endif == 0)) {
+ p->if_is_false = 0;
+ }
+ } else {
+ p->if_is_false = 1;
+
+ p->if_is_false_level = p->if_level;
+ }
+ p->if_level++;
+
+ break;
+ case SSI_ELIF: {
+ const char *expr = NULL;
+ for (i = 2; i < n; i += 2) {
+ if (0 == strcmp(l[i], "expr")) {
+ expr = l[i+1];
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: unknow attribute for ",
+ l[1], l[i]);
+ }
+ }
+
+ if (!expr) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "ssi: ",
+ l[1], "expr missing");
+ break;
+ }
+
+ p->if_level--;
+
+ if (p->if_level == p->if_is_false_level) {
+ if ((p->if_is_false) &&
+ (p->if_is_false_endif == 0)) {
+ switch (ssi_eval_expr(srv, con, p, expr)) {
+ case -1:
+ case 0:
+ p->if_is_false = 1;
+ p->if_is_false_level = p->if_level;
+ break;
+ case 1:
+ p->if_is_false = 0;
+ break;
+ }
+ } else {
+ p->if_is_false = 1;
+ p->if_is_false_level = p->if_level;
+ p->if_is_false_endif = 1;
+ }
+ }
+
+ p->if_level++;
+
+ break;
+ }
+ case SSI_ENDIF:
+ p->if_level--;
+
+ if (p->if_level == p->if_is_false_level) {
+ p->if_is_false = 0;
+ p->if_is_false_endif = 0;
+ }
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "ssi: unknow ssi-command:",
+ l[1]);
+ break;
+ }
+
+ return 0;
+
+}
+
+static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
+ stream s;
+#ifdef HAVE_PCRE_H
+ int i, n;
+
+#define N 10
+ int ovec[N * 3];
+#endif
+
+ /* get a stream to the file */
+
+ array_reset(p->ssi_vars);
+ array_reset(p->ssi_cgi_env);
+ buffer_copy_string(p->timefmt, "%a, %d %b %Y %H:%M:%S %Z");
+ p->sizefmt = 0;
+ build_ssi_cgi_vars(srv, con, p);
+ p->if_is_false = 0;
+
+ if (-1 == stream_open(&s, con->physical.path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "stream-open: ", con->physical.path);
+ return -1;
+ }
+
+
+ /**
+ * <!--#element attribute=value attribute=value ... -->
+ *
+ * config DONE
+ * errmsg -- missing
+ * sizefmt DONE
+ * timefmt DONE
+ * echo DONE
+ * var DONE
+ * encoding -- missing
+ * exec DONE
+ * cgi -- never
+ * cmd DONE
+ * fsize DONE
+ * file DONE
+ * virtual DONE
+ * flastmod DONE
+ * file DONE
+ * virtual DONE
+ * include DONE
+ * file DONE
+ * virtual DONE
+ * printenv DONE
+ * set DONE
+ * var DONE
+ * value DONE
+ *
+ * if DONE
+ * elif DONE
+ * else DONE
+ * endif DONE
+ *
+ *
+ * expressions
+ * AND, OR DONE
+ * comp DONE
+ * ${...} -- missing
+ * $... DONE
+ * '...' DONE
+ * ( ... ) DONE
+ *
+ *
+ *
+ * ** all DONE **
+ * DATE_GMT
+ * The current date in Greenwich Mean Time.
+ * DATE_LOCAL
+ * The current date in the local time zone.
+ * DOCUMENT_NAME
+ * The filename (excluding directories) of the document requested by the user.
+ * DOCUMENT_URI
+ * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document.
+ * LAST_MODIFIED
+ * The last modification date of the document requested by the user.
+ * USER_NAME
+ * Contains the owner of the file which included it.
+ *
+ */
+#ifdef HAVE_PCRE_H
+ for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) {
+ const char **l;
+ /* take every think from last offset to current match pos */
+
+ if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i);
+
+ pcre_get_substring_list(s.start, ovec, n, &l);
+ process_ssi_stmt(srv, con, p, l, n);
+ pcre_free_substring_list(l);
+ }
+
+ switch(n) {
+ case PCRE_ERROR_NOMATCH:
+ /* copy everything/the rest */
+ chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i);
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "execution error while matching: ", n);
+ break;
+ }
+#endif
+
+
+ stream_close(&s);
+
+ con->file_started = 1;
+ con->file_finished = 1;
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ /* reset physical.path */
+ buffer_reset(con->physical.path);
+
+ return 0;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
+ PATCH(ssi_extension);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_ssi_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(ssi_extension);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_ssi_physical_path) {
+ plugin_data *p = p_d;
+ size_t k, i;
+
+ if (con->physical.path->used == 0) return HANDLER_GO_ON;
+
+ mod_ssi_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_ssi_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ for (k = 0; k < p->conf.ssi_extension->used; k++) {
+ data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
+
+ if (ds->value->used == 0) continue;
+
+ if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
+ /* handle ssi-request */
+
+ if (mod_ssi_handle_request(srv, con, p)) {
+ /* on error */
+ con->http_status = 500;
+ }
+
+ return HANDLER_FINISHED;
+ }
+ }
+
+ /* not found */
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_ssi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("ssi");
+
+ p->init = mod_ssi_init;
+ p->handle_physical_path = mod_ssi_physical_path;
+ p->set_defaults = mod_ssi_set_defaults;
+ p->cleanup = mod_ssi_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_ssi.h b/src/mod_ssi.h
new file mode 100644
index 00000000..80f03ed8
--- /dev/null
+++ b/src/mod_ssi.h
@@ -0,0 +1,43 @@
+#ifndef _MOD_SSI_H_
+#define _MOD_SSI_H_
+
+#include "base.h"
+#include "buffer.h"
+#include "array.h"
+
+#include "plugin.h"
+
+#ifdef HAVE_PCRE_H
+#include <pcre.h>
+#endif
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ array *ssi_extension;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+#ifdef HAVE_PCRE_H
+ pcre *ssi_regex;
+#endif
+ buffer *timefmt;
+ int sizefmt;
+
+ buffer *stat_fn;
+
+ array *ssi_vars;
+ array *ssi_cgi_env;
+
+ int if_level, if_is_false_level, if_is_false, if_is_false_endif;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+int ssi_eval_expr(server *srv, connection *con, plugin_data *p, const char *expr);
+
+#endif
diff --git a/src/mod_ssi_expr.c b/src/mod_ssi_expr.c
new file mode 100644
index 00000000..98959abe
--- /dev/null
+++ b/src/mod_ssi_expr.c
@@ -0,0 +1,324 @@
+#include <ctype.h>
+#include <string.h>
+
+#include "buffer.h"
+#include "log.h"
+#include "mod_ssi.h"
+#include "mod_ssi_expr.h"
+#include "mod_ssi_exprparser.h"
+
+typedef struct {
+ const char *input;
+ size_t offset;
+ size_t size;
+
+ int line_pos;
+
+ int in_key;
+ int in_brace;
+ int in_cond;
+} ssi_tokenizer_t;
+
+ssi_val_t *ssi_val_init() {
+ ssi_val_t *s;
+
+ s = calloc(1, sizeof(*s));
+
+ return s;
+}
+
+void ssi_val_free(ssi_val_t *s) {
+ if (s->str) buffer_free(s->str);
+
+ free(s);
+}
+
+int ssi_val_tobool(ssi_val_t *B) {
+ if (B->type == SSI_TYPE_STRING) {
+ return B->str->used > 1 ? 1 : 0;
+ } else {
+ return B->bo;
+ }
+}
+
+static int ssi_expr_tokenizer(server *srv, connection *con, plugin_data *p,
+ ssi_tokenizer_t *t, int *token_id, buffer *token) {
+ int tid = 0;
+ size_t i;
+
+ UNUSED(con);
+
+ for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) {
+ char c = t->input[t->offset];
+ data_string *ds;
+
+ switch (c) {
+ case '=':
+ tid = TK_EQ;
+
+ t->offset++;
+ t->line_pos++;
+
+ buffer_copy_string(token, "(=)");
+
+ break;
+ case '>':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_GE;
+
+ buffer_copy_string(token, "(>=)");
+ } else {
+ t->offset += 1;
+ t->line_pos += 1;
+
+ tid = TK_GT;
+
+ buffer_copy_string(token, "(>)");
+ }
+
+ break;
+ case '<':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_LE;
+
+ buffer_copy_string(token, "(<=)");
+ } else {
+ t->offset += 1;
+ t->line_pos += 1;
+
+ tid = TK_LT;
+
+ buffer_copy_string(token, "(<)");
+ }
+
+ break;
+
+ case '!':
+ if (t->input[t->offset + 1] == '=') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_NE;
+
+ buffer_copy_string(token, "(!=)");
+ } else {
+ t->offset += 1;
+ t->line_pos += 1;
+
+ tid = TK_NOT;
+
+ buffer_copy_string(token, "(!)");
+ }
+
+ break;
+ case '&':
+ if (t->input[t->offset + 1] == '&') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_AND;
+
+ buffer_copy_string(token, "(&&)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing second &");
+ return -1;
+ }
+
+ break;
+ case '|':
+ if (t->input[t->offset + 1] == '|') {
+ t->offset += 2;
+ t->line_pos += 2;
+
+ tid = TK_OR;
+
+ buffer_copy_string(token, "(||)");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing second |");
+ return -1;
+ }
+
+ break;
+ case '\t':
+ case ' ':
+ t->offset++;
+ t->line_pos++;
+ break;
+
+ case '\'':
+ /* search for the terminating " */
+ for (i = 1; t->input[t->offset + i] && t->input[t->offset + i] != '\''; i++);
+
+ if (t->input[t->offset + i]) {
+ tid = TK_VALUE;
+
+ buffer_copy_string_len(token, t->input + t->offset + 1, i-1);
+
+ t->offset += i + 1;
+ t->line_pos += i + 1;
+ } else {
+ /* ERROR */
+
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing closing quote");
+
+ return -1;
+ }
+
+ break;
+ case '(':
+ t->offset++;
+ t->in_brace++;
+
+ tid = TK_LPARAN;
+
+ buffer_copy_string(token, "(");
+ break;
+ case ')':
+ t->offset++;
+ t->in_brace--;
+
+ tid = TK_RPARAN;
+
+ buffer_copy_string(token, ")");
+ break;
+ case '$':
+ if (t->input[t->offset + 1] == '{') {
+ for (i = 2; t->input[t->offset + i] && t->input[t->offset + i] != '}'; i++);
+
+ if (t->input[t->offset + i] != '}') {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "missing closing quote");
+
+ return -1;
+ }
+
+ buffer_copy_string_len(token, t->input + t->offset + 2, i-3);
+ } else {
+ for (i = 1; isalpha(t->input[t->offset + i]) || t->input[t->offset + i] == '_'; i++);
+
+ buffer_copy_string_len(token, t->input + t->offset + 1, i-1);
+ }
+
+ tid = TK_VALUE;
+
+ if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, token->ptr))) {
+ buffer_copy_string_buffer(token, ds->value);
+ } else if (NULL != (ds = (data_string *)array_get_element(p->ssi_vars, token->ptr))) {
+ buffer_copy_string_buffer(token, ds->value);
+ } else {
+ buffer_copy_string(token, "");
+ }
+
+ t->offset += i;
+ t->line_pos += i;
+
+ break;
+ default:
+ for (i = 0; isgraph(t->input[t->offset + i]); i++) {
+ char d = t->input[t->offset + i];
+ switch(d) {
+ case ' ':
+ case '\t':
+ case ')':
+ case '(':
+ case '\'':
+ case '=':
+ case '!':
+ case '<':
+ case '>':
+ case '&':
+ case '|':
+ break;
+ }
+ }
+
+ tid = TK_VALUE;
+
+ buffer_copy_string_len(token, t->input + t->offset, i);
+
+ t->offset += i;
+ t->line_pos += i;
+
+ break;
+ }
+ }
+
+ if (tid) {
+ *token_id = tid;
+
+ return 1;
+ } else if (t->offset < t->size) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t->line_pos,
+ "foobar");
+ }
+ return 0;
+}
+
+int ssi_eval_expr(server *srv, connection *con, plugin_data *p, const char *expr) {
+ ssi_tokenizer_t t;
+ void *pParser;
+ int token_id;
+ buffer *token;
+ ssi_ctx_t context;
+ int ret;
+
+ t.input = expr;
+ t.offset = 0;
+ t.size = strlen(expr);
+ t.line_pos = 1;
+
+ t.in_key = 1;
+ t.in_brace = 0;
+ t.in_cond = 0;
+
+ context.ok = 1;
+ context.srv = srv;
+
+ /* default context */
+
+ pParser = ssiexprparserAlloc( malloc );
+ token = buffer_init();
+ while((1 == (ret = ssi_expr_tokenizer(srv, con, p, &t, &token_id, token))) && context.ok) {
+ ssiexprparser(pParser, token_id, token, &context);
+
+ token = buffer_init();
+ }
+ ssiexprparser(pParser, 0, token, &context);
+ ssiexprparserFree(pParser, free );
+
+ buffer_free(token);
+
+ if (ret == -1) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "expr parser failed");
+ return -1;
+ }
+
+ if (context.ok == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "pos:", t.line_pos,
+ "parser failed somehow near here");
+ return -1;
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "expr: ",
+ expr,
+ context.val.bo);
+#endif
+ return context.val.bo;
+}
diff --git a/src/mod_ssi_expr.h b/src/mod_ssi_expr.h
new file mode 100644
index 00000000..b484f788
--- /dev/null
+++ b/src/mod_ssi_expr.h
@@ -0,0 +1,31 @@
+#ifndef _MOD_SSI_EXPR_H_
+#define _MOD_SSI_EXPR_H_
+
+#include "buffer.h"
+
+typedef struct {
+ enum { SSI_TYPE_UNSET, SSI_TYPE_BOOL, SSI_TYPE_STRING } type;
+
+ buffer *str;
+ int bo;
+} ssi_val_t;
+
+typedef struct {
+ int ok;
+
+ ssi_val_t val;
+
+ void *srv;
+} ssi_ctx_t;
+
+typedef enum { SSI_COND_UNSET, SSI_COND_LE, SSI_COND_GE, SSI_COND_EQ, SSI_COND_NE, SSI_COND_LT, SSI_COND_GT } ssi_expr_cond;
+
+void *ssiexprparserAlloc(void *(*mallocProc)(size_t));
+void ssiexprparserFree(void *p, void (*freeProc)(void*));
+void ssiexprparser(void *yyp, int yymajor, buffer *yyminor, ssi_ctx_t *ctx);
+
+int ssi_val_tobool(ssi_val_t *B);
+ssi_val_t *ssi_val_init();
+void ssi_val_free(ssi_val_t *s);
+
+#endif
diff --git a/src/mod_ssi_exprparser.h b/src/mod_ssi_exprparser.h
new file mode 100644
index 00000000..eb55ea5f
--- /dev/null
+++ b/src/mod_ssi_exprparser.h
@@ -0,0 +1,12 @@
+#define TK_AND 1
+#define TK_OR 2
+#define TK_EQ 3
+#define TK_NE 4
+#define TK_GT 5
+#define TK_GE 6
+#define TK_LT 7
+#define TK_LE 8
+#define TK_NOT 9
+#define TK_LPARAN 10
+#define TK_RPARAN 11
+#define TK_VALUE 12
diff --git a/src/mod_ssi_exprparser.y b/src/mod_ssi_exprparser.y
new file mode 100644
index 00000000..1bf229b7
--- /dev/null
+++ b/src/mod_ssi_exprparser.y
@@ -0,0 +1,119 @@
+%token_prefix TK_
+%token_type {buffer *}
+%extra_argument {ssi_ctx_t *ctx}
+%name ssiexprparser
+
+%include {
+#include <assert.h>
+#include <string.h>
+#include "config.h"
+#include "mod_ssi_expr.h"
+#include "buffer.h"
+}
+
+%parse_failure {
+ ctx->ok = 0;
+}
+
+%type expr { ssi_val_t * }
+%type value { buffer * }
+%type exprline { ssi_val_t * }
+%type cond { int }
+%token_destructor { buffer_free($$); }
+
+%left AND.
+%left OR.
+%nonassoc EQ NE GT GE LT LE.
+%right NOT.
+
+input ::= exprline(B). {
+ ctx->val.bo = ssi_val_tobool(B);
+ ctx->val.type = SSI_TYPE_BOOL;
+
+ ssi_val_free(B);
+}
+
+exprline(A) ::= expr(B) cond(C) expr(D). {
+ int cmp;
+
+ if (B->type == SSI_TYPE_STRING &&
+ D->type == SSI_TYPE_STRING) {
+ cmp = strcmp(B->str->ptr, D->str->ptr);
+ } else {
+ cmp = ssi_val_tobool(B) - ssi_val_tobool(D);
+ }
+
+ A = B;
+
+ switch(C) {
+ case SSI_COND_EQ: A->bo = (cmp == 0) ? 1 : 0; break;
+ case SSI_COND_NE: A->bo = (cmp != 0) ? 1 : 0; break;
+ case SSI_COND_GE: A->bo = (cmp >= 0) ? 1 : 0; break;
+ case SSI_COND_GT: A->bo = (cmp > 0) ? 1 : 0; break;
+ case SSI_COND_LE: A->bo = (cmp <= 0) ? 1 : 0; break;
+ case SSI_COND_LT: A->bo = (cmp < 0) ? 1 : 0; break;
+ }
+
+ A->type = SSI_TYPE_BOOL;
+
+ ssi_val_free(D);
+}
+exprline(A) ::= expr(B). {
+ A = B;
+}
+expr(A) ::= expr(B) AND expr(C). {
+ int e;
+
+ e = ssi_val_tobool(B) && ssi_val_tobool(C);
+
+ A = B;
+ A->bo = e;
+ A->type = SSI_TYPE_BOOL;
+ ssi_val_free(C);
+}
+
+expr(A) ::= expr(B) OR expr(C). {
+ int e;
+
+ e = ssi_val_tobool(B) || ssi_val_tobool(C);
+
+ A = B;
+ A->bo = e;
+ A->type = SSI_TYPE_BOOL;
+ ssi_val_free(C);
+}
+
+expr(A) ::= NOT expr(B). {
+ int e;
+
+ e = !ssi_val_tobool(B);
+
+ A = B;
+ A->bo = e;
+ A->type = SSI_TYPE_BOOL;
+}
+expr(A) ::= LPARAN exprline(B) RPARAN. {
+ A = B;
+}
+
+expr(A) ::= value(B). {
+ A = ssi_val_init();
+ A->str = B;
+ A->type = SSI_TYPE_STRING;
+}
+
+value(A) ::= VALUE(B). {
+ A = buffer_init_string(B->ptr);
+}
+
+value(A) ::= value(B) VALUE(C). {
+ A = B;
+ buffer_append_string_buffer(A, C);
+}
+
+cond(A) ::= EQ. { A = SSI_COND_EQ; }
+cond(A) ::= NE. { A = SSI_COND_NE; }
+cond(A) ::= LE. { A = SSI_COND_LE; }
+cond(A) ::= GE. { A = SSI_COND_GE; }
+cond(A) ::= LT. { A = SSI_COND_LT; }
+cond(A) ::= GT. { A = SSI_COND_GT; }
diff --git a/src/mod_status.c b/src/mod_status.c
new file mode 100644
index 00000000..ed51f329
--- /dev/null
+++ b/src/mod_status.c
@@ -0,0 +1,653 @@
+#define _GNU_SOURCE
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include "server.h"
+#include "connections.h"
+#include "response.h"
+#include "connections.h"
+#include "log.h"
+
+#include "plugin.h"
+
+#include "inet_ntop_cache.h"
+
+typedef struct {
+ buffer *config_url;
+ buffer *status_url;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ double traffic_out;
+ double requests;
+
+ double mod_5s_traffic_out[5];
+ double mod_5s_requests[5];
+ size_t mod_5s_ndx;
+
+ double rel_traffic_out;
+ double rel_requests;
+
+ double abs_traffic_out;
+ double abs_requests;
+
+ double bytes_written;
+
+ buffer *module_list;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+INIT_FUNC(mod_status_init) {
+ plugin_data *p;
+ size_t i;
+
+ p = calloc(1, sizeof(*p));
+
+ p->traffic_out = p->requests = 0;
+ p->rel_traffic_out = p->rel_requests = 0;
+ p->abs_traffic_out = p->abs_requests = 0;
+ p->bytes_written = 0;
+ p->module_list = buffer_init();
+
+ for (i = 0; i < 5; i++) {
+ p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0;
+ }
+
+ return p;
+}
+
+FREE_FUNC(mod_status_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ buffer_free(p->module_list);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->status_url);
+ buffer_free(s->config_url);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_status_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "status.status-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "status.config-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->config_url = buffer_init();
+ s->status_url = buffer_init();
+
+ cv[0].destination = s->status_url;
+ cv[1].destination = s->config_url;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+
+static int mod_status_row_append(buffer *b, const char *key, const char *value) {
+ BUFFER_APPEND_STRING_CONST(b, " <tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, " <td><b>");
+ buffer_append_string(b, key);
+ BUFFER_APPEND_STRING_CONST(b, "</b></td>\n");
+ BUFFER_APPEND_STRING_CONST(b, " <td>");
+ buffer_append_string(b, value);
+ BUFFER_APPEND_STRING_CONST(b, "</td>\n");
+ BUFFER_APPEND_STRING_CONST(b, " </tr>\n");
+
+ return 0;
+}
+
+static int mod_status_header_append(buffer *b, const char *key) {
+ BUFFER_APPEND_STRING_CONST(b, " <tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, " <th colspan=\"2\">");
+ buffer_append_string(b, key);
+ BUFFER_APPEND_STRING_CONST(b, "</th>\n");
+ BUFFER_APPEND_STRING_CONST(b, " </tr>\n");
+
+ return 0;
+}
+
+static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ buffer *b;
+ size_t j;
+ double avg;
+ char multiplier = '\0';
+ char buf[32];
+ time_t ts;
+
+ int days, hours, mins, seconds;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ BUFFER_COPY_STRING_CONST(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>Status</title>\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " <style type=\"text/css\">\n"
+ "th.status { background-color: black; color: white; }\n"
+ "table.status { border: black solid thin; }\n"
+ "td.int { background-color: #f0f0f0; text-align: right }\n"
+ "td.string { background-color: #f0f0f0; text-align: left }\n"
+ " </style>\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </head>\n"
+ " <body>\n");
+
+
+
+ /* connection listing */
+ BUFFER_APPEND_STRING_CONST(b, "<h1>Server-Status</h1>");
+
+ BUFFER_APPEND_STRING_CONST(b, "<table class=\"status\">");
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Hostname</td><td class=\"string\">");
+ buffer_append_string_buffer(b, con->uri.authority);
+ BUFFER_APPEND_STRING_CONST(b, " (");
+ buffer_append_string_buffer(b, con->server_name);
+ BUFFER_APPEND_STRING_CONST(b, ")</td></tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Uptime</td><td class=\"string\">");
+
+ ts = srv->cur_ts - srv->startup_ts;
+
+ days = ts / (60 * 60 * 24);
+ ts %= (60 * 60 * 24);
+
+ hours = ts / (60 * 60);
+ ts %= (60 * 60);
+
+ mins = ts / (60);
+ ts %= (60);
+
+ seconds = ts;
+
+ if (days) {
+ buffer_append_long(b, days);
+ BUFFER_APPEND_STRING_CONST(b, " days ");
+ }
+
+ if (hours) {
+ buffer_append_long(b, hours);
+ BUFFER_APPEND_STRING_CONST(b, " hours ");
+ }
+
+ if (mins) {
+ buffer_append_long(b, mins);
+ BUFFER_APPEND_STRING_CONST(b, " min ");
+ }
+
+ buffer_append_long(b, seconds);
+ BUFFER_APPEND_STRING_CONST(b, " s");
+
+ BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n");
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Started at</td><td class=\"string\">");
+
+ ts = srv->startup_ts;
+
+ strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts));
+ buffer_append_string(b, buf);
+ BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">");
+ avg = p->abs_requests;
+ multiplier = '\0';
+ if (avg > 1000) { avg /= 1000; multiplier = 'k'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'M'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'G'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'T'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'P'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'E'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'Z'; }
+ if (avg > 1000) { avg /= 1000; multiplier = 'Y'; }
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "req</td></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">");
+ avg = p->abs_traffic_out;
+ multiplier = '\0';
+
+ if (avg > 1024) { avg /= 1024; multiplier = 'k'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'M'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'G'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'T'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'P'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'E'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Z'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Y'; }
+
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "byte</td></tr>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n");
+ for (j = 0, avg = 0; j < 5; j++) {
+ avg += p->mod_5s_requests[j];
+ }
+
+ avg /= 5;
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">");
+ multiplier = '\0';
+
+ if (avg > 1024) { avg /= 1024; multiplier = 'k'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'M'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'G'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'T'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'P'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'E'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Z'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Y'; }
+
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+
+ BUFFER_APPEND_STRING_CONST(b, "req/s</td></tr>\n");
+
+ for (j = 0, avg = 0; j < 5; j++) {
+ avg += p->mod_5s_traffic_out[j];
+ }
+
+ avg /= 5;
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">");
+ multiplier = '\0';
+
+ if (avg > 1024) { avg /= 1024; multiplier = 'k'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'M'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'G'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'T'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'P'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'E'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Z'; }
+ if (avg > 1024) { avg /= 1024; multiplier = 'Y'; }
+
+ buffer_append_long(b, avg);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ if (multiplier) buffer_append_string_len(b, &multiplier, 1);
+ BUFFER_APPEND_STRING_CONST(b, "byte/s</td></tr>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "</table>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b, "<hr />\n<pre><b>legend</b>\n");
+ BUFFER_APPEND_STRING_CONST(b, ". = connect, C = close, E = hard error\n");
+ BUFFER_APPEND_STRING_CONST(b, "r = read, R = read-POST, W = write, h = handle-request\n");
+ BUFFER_APPEND_STRING_CONST(b, "q = request-start, Q = request-end\n");
+ BUFFER_APPEND_STRING_CONST(b, "s = response-start, S = response-end\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<b>");
+ buffer_append_long(b, srv->conns->used);
+ BUFFER_APPEND_STRING_CONST(b, " connections</b>\n");
+
+ for (j = 0; j < srv->conns->used; j++) {
+ connection *c = srv->conns->ptr[j];
+ const char *state = connection_get_short_state(c->state);
+
+ buffer_append_string_len(b, state, 1);
+
+ if (((j + 1) % 50) == 0) {
+ BUFFER_APPEND_STRING_CONST(b, "\n");
+ }
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\n</pre><hr />\n<h2>Connections</h2>\n");
+
+ BUFFER_APPEND_STRING_CONST(b, "<table class=\"status\">\n");
+ BUFFER_APPEND_STRING_CONST(b, "<tr>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Client IP</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Read</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Written</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">State</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Time</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Host</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">URI</th>");
+ BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">File</th>");
+ BUFFER_APPEND_STRING_CONST(b, "</tr>\n");
+
+ for (j = 0; j < srv->conns->used; j++) {
+ connection *c = srv->conns->ptr[j];
+
+ BUFFER_APPEND_STRING_CONST(b, "<tr><td class=\"string\">");
+
+ buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(c->dst_addr)));
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">");
+
+ if (con->request.content_length) {
+ buffer_append_long(b, c->request.content->used);
+ BUFFER_APPEND_STRING_CONST(b, "/");
+ buffer_append_long(b, c->request.content_length);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "0/0");
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">");
+
+ buffer_append_off_t(b, chunkqueue_written(c->write_queue));
+ BUFFER_APPEND_STRING_CONST(b, "/");
+ buffer_append_off_t(b, chunkqueue_length(c->write_queue));
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string(b, connection_get_state(c->state));
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">");
+
+ buffer_append_long(b, srv->cur_ts - c->request_start);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string_buffer(b, c->server_name);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string_html_encoded(b, c->uri.path->ptr);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">");
+
+ buffer_append_string_buffer(b, c->physical.path);
+
+ BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n");
+ }
+
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "</table>\n");
+
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </body>\n"
+ "</html>\n"
+ );
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+}
+
+static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ buffer *b, *m = p->module_list;
+ size_t i;
+
+ struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
+ {
+ /* - poll is most reliable
+ * - select works everywhere
+ * - linux-* are experimental
+ */
+#ifdef USE_POLL
+ { FDEVENT_HANDLER_POLL, "poll" },
+#endif
+#ifdef USE_SELECT
+ { FDEVENT_HANDLER_SELECT, "select" },
+#endif
+#ifdef USE_LINUX_EPOLL
+ { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
+#endif
+#ifdef USE_LINUX_SIGIO
+ { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" },
+#endif
+#ifdef USE_SOLARIS_DEVPOLL
+ { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
+#endif
+#ifdef USE_FREEBSD_KQUEUE
+ { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
+#endif
+ { FDEVENT_HANDLER_UNSET, NULL }
+ };
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ BUFFER_COPY_STRING_CONST(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>Status</title>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
+ " <table border=\"1\">\n");
+
+ mod_status_header_append(b, "Server-Features");
+#ifdef HAVE_PCRE_H
+ mod_status_row_append(b, "Rewrite Engine", "enabled");
+#else
+ mod_status_row_append(b, "Rewrite Engine", "disabled - pcre missing");
+#endif
+#ifdef HAVE_ZLIB_H
+ mod_status_row_append(b, "On-the-Fly Output Compression", "enabled");
+#else
+ mod_status_row_append(b, "On-the-Fly Output Compression", "disabled - zlib missing");
+#endif
+ mod_status_header_append(b, "Network Engine");
+
+ for (i = 0; event_handlers[i].name; i++) {
+ if (event_handlers[i].et == srv->event_handler) {
+ mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name);
+ break;
+ }
+ }
+
+ mod_status_header_append(b, "Config-File-Settings");
+ mod_status_row_append(b, "Directory Listings", con->conf.dir_listing ? "enabled" : "disabled");
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin **ps = srv->plugins.ptr;
+
+ plugin *pl = ps[i];
+
+ if (i == 0) {
+ buffer_copy_string_buffer(m, pl->name);
+ } else {
+ BUFFER_APPEND_STRING_CONST(m, "<br />");
+ buffer_append_string_buffer(m, pl->name);
+ }
+ }
+
+ mod_status_row_append(b, "Loaded Modules", m->ptr);
+
+ BUFFER_APPEND_STRING_CONST(b, " </table>\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </body>\n"
+ "</html>\n"
+ );
+
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ con->http_status = 200;
+ con->file_finished = 1;
+
+ return HANDLER_FINISHED;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_skeleton_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) {
+ PATCH(status_url);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) {
+ PATCH(config_url);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_skeleton_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(status_url);
+ PATCH(config_url);
+
+ return 0;
+}
+#undef PATCH
+
+static handler_t mod_status_handler(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ mod_skeleton_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_skeleton_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (!buffer_is_empty(p->conf.status_url) &&
+ buffer_is_equal(p->conf.status_url, con->uri.path)) {
+ return mod_status_handle_server_status(srv, con, p_d);
+ } else if (!buffer_is_empty(p->conf.config_url) &&
+ buffer_is_equal(p->conf.config_url, con->uri.path)) {
+ return mod_status_handle_server_config(srv, con, p_d);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+TRIGGER_FUNC(mod_status_trigger) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ /* check all connections */
+ for (i = 0; i < srv->conns->used; i++) {
+ connection *c = srv->conns->ptr[i];
+
+ p->bytes_written += c->bytes_written_cur_second;
+ }
+
+ /* a sliding average */
+ p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
+ p->mod_5s_requests [p->mod_5s_ndx] = p->requests;
+
+ p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
+
+ p->abs_traffic_out += p->bytes_written;
+ p->rel_traffic_out += p->bytes_written;
+
+ p->bytes_written = 0;
+
+ /* reset storage - second */
+ p->traffic_out = 0;
+ p->requests = 0;
+
+ return HANDLER_GO_ON;
+}
+
+REQUESTDONE_FUNC(mod_status_account) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ p->requests++;
+ p->rel_requests++;
+ p->abs_requests++;
+
+ p->bytes_written += con->bytes_written_cur_second;
+
+ return HANDLER_GO_ON;
+}
+
+int mod_status_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("status");
+
+ p->init = mod_status_init;
+ p->cleanup = mod_status_free;
+ p->set_defaults= mod_status_set_defaults;
+
+ p->handle_uri_clean = mod_status_handler;
+ p->handle_trigger = mod_status_trigger;
+ p->handle_request_done = mod_status_account;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_userdir.c b/src/mod_userdir.c
new file mode 100644
index 00000000..d098fbc2
--- /dev/null
+++ b/src/mod_userdir.c
@@ -0,0 +1,248 @@
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "response.h"
+
+#include "plugin.h"
+
+#include "config.h"
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+/* plugin config for all request/connections */
+typedef struct {
+ array *exclude_user;
+ array *include_user;
+ buffer *path;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ buffer *username;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_userdir_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ p->username = buffer_init();
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_userdir_free) {
+ plugin_data *p = p_d;
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ array_free(s->include_user);
+ array_free(s->exclude_user);
+ buffer_free(s->path);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ buffer_free(p->username);
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_userdir_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i;
+
+ config_values_t cv[] = {
+ { "userdir.path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "userdir.exclude-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "userdir.include-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->exclude_user = array_init();
+ s->include_user = array_init();
+ s->path = buffer_init();
+
+ cv[0].destination = s->path;
+ cv[1].destination = s->exclude_user;
+ cv[2].destination = s->include_user;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_userdir_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.path"))) {
+ PATCH(path);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.exclude-user"))) {
+ PATCH(exclude_user);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.include-user"))) {
+ PATCH(include_user);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_userdir_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(path);
+ PATCH(exclude_user);
+ PATCH(include_user);
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(mod_userdir_docroot_handler) {
+ plugin_data *p = p_d;
+ int uri_len;
+ size_t k;
+ char *rel_url;
+ struct passwd *pwd;
+ size_t i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+#ifdef HAVE_PWD_H
+ mod_userdir_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_userdir_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ uri_len = con->uri.path->used - 1;
+
+ /* /~user/foo.html -> /home/user/public_html/foo.html */
+
+ if (con->uri.path->ptr[0] != '/' ||
+ con->uri.path->ptr[1] != '~') return HANDLER_GO_ON;
+
+ if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) {
+ /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
+ http_response_redirect_to_directory(srv, con);
+
+ return HANDLER_FINISHED;
+ }
+ buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2));
+
+ if (NULL == (pwd = getpwnam(p->username->ptr))) {
+ /* user not found */
+ return HANDLER_GO_ON;
+ }
+
+ for (k = 0; k < p->conf.exclude_user->used; k++) {
+ data_string *ds = (data_string *)p->conf.exclude_user->data[k];
+
+ if (buffer_is_equal(ds->value, p->username)) {
+ /* user in exclude list */
+ return HANDLER_GO_ON;
+ }
+ }
+
+ if (p->conf.include_user->used) {
+ int found_user = 0;
+ for (k = 0; k < p->conf.include_user->used; k++) {
+ data_string *ds = (data_string *)p->conf.include_user->data[k];
+
+ if (buffer_is_equal(ds->value, p->username)) {
+ /* user in exclude list */
+ found_user = 1;
+ break;
+ }
+ }
+
+ if (!found_user) return HANDLER_GO_ON;
+ }
+
+ buffer_copy_string(con->physical.path, pwd->pw_dir);
+ BUFFER_APPEND_SLASH(con->physical.path);
+ buffer_append_string_buffer(con->physical.path, p->conf.path); /* skip the / */
+ BUFFER_APPEND_SLASH(con->physical.path);
+ buffer_append_string(con->physical.path, rel_url + 1); /* skip the / */
+
+ return HANDLER_GO_ON;
+#else
+ return HANDLER_ERROR;
+#endif
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_userdir_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("userdir");
+
+ p->init = mod_userdir_init;
+ p->handle_docroot = mod_userdir_docroot_handler;
+ p->set_defaults = mod_userdir_set_defaults;
+ p->cleanup = mod_userdir_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/mod_usertrack.c b/src/mod_usertrack.c
new file mode 100644
index 00000000..7b53fddf
--- /dev/null
+++ b/src/mod_usertrack.c
@@ -0,0 +1,247 @@
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "log.h"
+#include "buffer.h"
+
+#include "plugin.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/md5.h>
+#else
+# include "md5_global.h"
+# include "md5.h"
+#endif
+
+/* plugin config for all request/connections */
+
+typedef struct {
+ buffer *cookie_name;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+
+ plugin_config **config_storage;
+
+ plugin_config conf;
+} plugin_data;
+
+/* init the plugin data */
+INIT_FUNC(mod_usertrack_init) {
+ plugin_data *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+/* detroy the plugin data */
+FREE_FUNC(mod_usertrack_free) {
+ plugin_data *p = p_d;
+
+ UNUSED(srv);
+
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ buffer_free(s->cookie_name);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+/* handle plugin config and check values */
+
+SETDEFAULTS_FUNC(mod_usertrack_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "usertrack.cookiename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *));
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s;
+
+ s = malloc(sizeof(plugin_config));
+ s->cookie_name = buffer_init();
+
+ cv[0].destination = s->cookie_name;
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
+ return HANDLER_ERROR;
+ }
+
+ if (s->cookie_name->used == 0) {
+ buffer_copy_string(s->cookie_name, "TRACKID");
+ } else {
+ size_t j;
+ for (j = 0; j < s->cookie_name->used - 1; j++) {
+ char c = s->cookie_name->ptr[j] | 32;
+ if (c < 'a' || c > 'z') {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "invalid character in usertrack.cookiename:",
+ s->cookie_name);
+
+ return HANDLER_ERROR;
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_usertrack_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) {
+ size_t i, j;
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ plugin_config *s = p->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue;
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookiename"))) {
+ PATCH(cookie_name);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mod_usertrack_setup_connection(server *srv, connection *con, plugin_data *p) {
+ plugin_config *s = p->config_storage[0];
+ UNUSED(srv);
+ UNUSED(con);
+
+ PATCH(cookie_name);
+
+ return 0;
+}
+#undef PATCH
+
+
+
+URIHANDLER_FUNC(mod_usertrack_uri_handler) {
+ plugin_data *p = p_d;
+ data_string *ds;
+ unsigned char h[16];
+ MD5_CTX Md5Ctx;
+ char hh[32];
+ size_t i;
+
+ if (con->uri.path->used == 0) return HANDLER_GO_ON;
+
+ mod_usertrack_setup_connection(srv, con, p);
+ for (i = 0; i < srv->config_patches->used; i++) {
+ buffer *patch = srv->config_patches->ptr[i];
+
+ mod_usertrack_patch_connection(srv, con, p, CONST_BUF_LEN(patch));
+ }
+
+ if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) {
+ char *g;
+ /* we have a cookie, does it contain a valid name ? */
+
+ /* parse the cookie
+ *
+ * check for cookiename + (WS | '=')
+ *
+ */
+
+ if (NULL != (g = strstr(ds->value->ptr, p->conf.cookie_name->ptr))) {
+ char *nc;
+
+ /* skip WS */
+ for (nc = g + p->conf.cookie_name->used-1; *nc == ' ' || *nc == '\t'; nc++);
+
+ if (*nc == '=') {
+ /* ok, found the key of our own cookie */
+
+ if (strlen(nc) > 32) {
+ /* i'm lazy */
+ return HANDLER_GO_ON;
+ }
+ }
+ }
+ }
+
+ /* set a cookie */
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string(ds->key, "Set-Cookie");
+ buffer_copy_string_buffer(ds->value, p->conf.cookie_name);
+ buffer_append_string(ds->value, "=");
+
+
+ /* taken from mod_auth.c */
+
+ /* generate shared-secret */
+ MD5_Init(&Md5Ctx);
+ MD5_Update(&Md5Ctx, (unsigned char *)con->uri.path->ptr, con->uri.path->used - 1);
+ MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
+
+ /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
+ ltostr(hh, srv->cur_ts);
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+ ltostr(hh, rand());
+ MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
+
+ MD5_Final(h, &Md5Ctx);
+
+ buffer_append_string_hex(ds->value, (char *)h, 16);
+ buffer_append_string(ds->value, "; path=/");
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ return HANDLER_GO_ON;
+}
+
+/* this function is called at dlopen() time and inits the callbacks */
+
+int mod_usertrack_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("usertrack");
+
+ p->init = mod_usertrack_init;
+ p->handle_uri_clean = mod_usertrack_uri_handler;
+ p->set_defaults = mod_usertrack_set_defaults;
+ p->cleanup = mod_usertrack_free;
+
+ p->data = NULL;
+
+ return 0;
+}
diff --git a/src/network.c b/src/network.c
new file mode 100644
index 00000000..bcf289fd
--- /dev/null
+++ b/src/network.c
@@ -0,0 +1,517 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+#include "connections.h"
+#include "plugin.h"
+#include "joblist.h"
+
+#include "network_backends.h"
+#include "sys-mmap.h"
+#include "sys-socket.h"
+
+#ifdef USE_OPENSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/rand.h>
+#endif
+
+handler_t network_server_handle_fdevent(void *s, void *context, int revents) {
+ server *srv = (server *)s;
+ server_socket *srv_socket = (server_socket *)context;
+ connection *con;
+
+ UNUSED(context);
+
+ if (revents != FDEVENT_IN) {
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "strange event for server socket",
+ srv_socket->fd,
+ revents);
+ return -1;
+ }
+
+ while (NULL != (con = connection_accept(srv, srv_socket))) {
+ handler_t r;
+
+ connection_state_machine(srv, con);
+
+ switch(r = plugins_call_handle_joblist(srv, con)) {
+ case HANDLER_FINISHED:
+ case HANDLER_GO_ON:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+ }
+ return HANDLER_GO_ON;
+}
+
+int network_server_init(server *srv, buffer *host_token, specific_config *s) {
+ int val;
+ socklen_t addr_len;
+ server_socket *srv_socket;
+ char *sp;
+ unsigned int port = 0;
+ const char *host;
+ buffer *b;
+
+#ifdef SO_ACCEPTFILTER
+ struct accept_filter_arg afa;
+#endif
+
+#ifdef __WIN32
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+
+ wVersionRequested = MAKEWORD( 2, 2 );
+
+ err = WSAStartup( wVersionRequested, &wsaData );
+ if ( err != 0 ) {
+ /* Tell the user that we could not find a usable */
+ /* WinSock DLL. */
+ return -1;
+ }
+#endif
+
+ srv_socket = calloc(1, sizeof(*srv_socket));
+ srv_socket->fd = -1;
+
+ srv_socket->srv_token = buffer_init();
+ buffer_copy_string_buffer(srv_socket->srv_token, host_token);
+
+ b = buffer_init();
+ buffer_copy_string_buffer(b, host_token);
+
+ /* ipv4:port
+ * [ipv6]:port
+ */
+ if (NULL == (sp = strrchr(b->ptr, ':'))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "value of $SERVER[\"socket\"] has to be \"ip:port\".", b);
+
+ return -1;
+ }
+
+ host = b->ptr;
+
+ /* check for [ and ] */
+ if (b->ptr[0] == '[' && *(sp-1) == ']') {
+ *(sp-1) = '\0';
+ host++;
+
+ s->use_ipv6 = 1;
+ }
+
+ *(sp++) = '\0';
+
+ port = strtol(sp, NULL, 10);
+
+ if (port == 0 || port > 65535) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "port out of range:", port);
+
+ return -1;
+ }
+
+ if (*host == '\0') host = NULL;
+
+#ifdef HAVE_IPV6
+ if (s->use_ipv6) {
+ srv_socket->addr.plain.sa_family = AF_INET6;
+
+ if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
+ return -1;
+ }
+ srv_socket->use_ipv6 = 1;
+ }
+#endif
+
+ if (srv_socket->fd == -1) {
+ srv_socket->addr.plain.sa_family = AF_INET;
+ if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* */
+ srv->cur_fds = srv_socket->fd;
+
+ val = 1;
+ if (setsockopt(srv_socket->fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "socketsockopt failed:", strerror(errno));
+ return -1;
+ }
+
+ if (-1 == fdevent_fcntl_set(srv->ev, srv_socket->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed:", strerror(errno));
+ return -1;
+ }
+
+ switch(srv_socket->addr.plain.sa_family) {
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in6));
+ srv_socket->addr.ipv6.sin6_family = AF_INET6;
+ if (host == NULL) {
+ srv_socket->addr.ipv6.sin6_addr = in6addr_any;
+ } else {
+ struct addrinfo hints, *res;
+ int r;
+
+ memset(&hints, 0, sizeof(hints));
+
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ if (0 != (r = getaddrinfo(host, NULL, &hints, &res))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "sssss", "getaddrinfo failed: ",
+ gai_strerror(r), "'", host, "'");
+
+ return -1;
+ }
+
+ memcpy(&(srv_socket->addr), res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+ }
+ srv_socket->addr.ipv6.sin6_port = htons(port);
+ addr_len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+ case AF_INET:
+ memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in));
+ srv_socket->addr.ipv4.sin_family = AF_INET;
+ if (host == NULL) {
+ srv_socket->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct hostent *he;
+ if (NULL == (he = gethostbyname(host))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "sss", "gethostbyname failed: ",
+ hstrerror(h_errno), host);
+ return -1;
+ }
+
+ if (he->h_addrtype != AF_INET) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype);
+ return -1;
+ }
+
+ if (he->h_length != sizeof(struct in_addr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length);
+ return -1;
+ }
+
+ memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr), he->h_addr_list[0], he->h_length);
+ }
+ srv_socket->addr.ipv4.sin_port = htons(port);
+
+ addr_len = sizeof(struct sockaddr_in);
+
+ break;
+ default:
+ addr_len = 0;
+
+ return -1;
+ }
+
+ if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "can't bind to port", port, strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(srv_socket->fd, 128 * 8)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno));
+ return -1;
+ }
+
+#ifdef SO_ACCEPTFILTER
+ /*
+ * FreeBSD accf_http filter
+ *
+ */
+ memset(&afa, 0, sizeof(afa));
+ strcpy(afa.af_name, "httpready");
+ if (setsockopt(srv_socket->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) {
+ if (errno != ENOENT) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "can't set accept-filter 'httpready': ", strerror(errno));
+ }
+ }
+#endif
+
+
+ if (s->is_ssl) {
+#ifdef USE_OPENSSL
+ if (srv->ssl_is_init == 0) {
+ SSL_load_error_strings();
+ SSL_library_init();
+ srv->ssl_is_init = 1;
+
+ if (0 == RAND_status()) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ "not enough entropy in the pool");
+ return -1;
+ }
+ }
+
+ if (NULL == (s->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (buffer_is_empty(s->ssl_pemfile)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "ssl.pemfile has to be set");
+ return -1;
+ }
+
+ if (0 > SSL_CTX_use_certificate_file(s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (0 > SSL_CTX_use_PrivateKey_file (s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (!SSL_CTX_check_private_key(s->ssl_ctx)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ "Private key does not match the certificate public key");
+ return -1;
+ }
+ srv_socket->ssl_ctx = s->ssl_ctx;
+#else
+
+ buffer_free(srv_socket->srv_token);
+ free(srv_socket);
+
+ buffer_free(b);
+
+ log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
+ "ssl requested but openssl support is not compiled in");
+
+ return -1;
+#endif
+ }
+
+ srv_socket->is_ssl = s->is_ssl;
+ srv_socket->fde_ndx = -1;
+
+ if (srv->srv_sockets.size == 0) {
+ srv->srv_sockets.size = 4;
+ srv->srv_sockets.used = 0;
+ srv->srv_sockets.ptr = malloc(srv->srv_sockets.size * sizeof(server_socket));
+ } else if (srv->srv_sockets.used == srv->srv_sockets.size) {
+ srv->srv_sockets.size += 4;
+ srv->srv_sockets.ptr = realloc(srv->srv_sockets.ptr, srv->srv_sockets.size * sizeof(server_socket));
+ }
+
+ srv->srv_sockets.ptr[srv->srv_sockets.used++] = srv_socket;
+
+ buffer_free(b);
+
+ return 0;
+}
+
+int network_close(server *srv) {
+ size_t i;
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+
+ if (srv_socket->fd) {
+ /* check if server fd are already registered */
+ if (srv_socket->fde_ndx != -1) {
+ fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd);
+ fdevent_unregister(srv->ev, srv_socket->fd);
+ }
+
+ close(srv_socket->fd);
+ }
+
+ buffer_free(srv_socket->srv_token);
+
+ free(srv_socket);
+ }
+
+ free(srv->srv_sockets.ptr);
+
+ return 0;
+}
+
+int network_init(server *srv) {
+ buffer *b;
+ size_t i;
+
+ b = buffer_init();
+
+ buffer_copy_string_buffer(b, srv->srvconf.bindhost);
+ buffer_append_string(b, ":");
+ buffer_append_long(b, srv->srvconf.port);
+
+ if (0 != network_server_init(srv, b, srv->config_storage[0])) {
+ return -1;
+ }
+ buffer_free(b);
+
+ /* check for $SERVER["socket"] */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ specific_config *s = srv->config_storage[i];
+
+ /* not our stage */
+ if (!buffer_is_equal_string(dc->comp_key, CONST_STR_LEN("SERVERsocket"))) continue;
+
+ if (dc->cond != CONFIG_COND_EQ) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "only == is allowed for $SERVER[\"socket\"].");
+
+ return -1;
+ }
+
+ if (0 != network_server_init(srv, dc->match.string, s)) {
+ return -1;
+ }
+ }
+
+
+ return 0;
+}
+
+int network_register_fdevents(server *srv) {
+ size_t i;
+
+ fdevent_reset(srv->ev);
+
+ /* register fdevents after reset */
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+
+ fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);
+ fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
+ }
+ return 0;
+}
+
+int network_write_chunkqueue(server *srv, connection *con, chunkqueue *cq) {
+ int ret = -1, i;
+ off_t written = 0;
+
+ server_socket *srv_socket = con->srv_socket;
+
+ if (con->conf.global_kbytes_per_second &&
+ *(con->conf.global_bytes_per_second_cnt_ptr) > con->conf.global_kbytes_per_second * 1024) {
+ /* we reached the global traffic limit */
+
+ con->traffic_limit_reached = 1;
+ joblist_append(srv, con);
+
+ return 1;
+ }
+
+ written = con->bytes_written;
+
+#ifdef TCP_CORK
+ /* Linux: put a cork into the socket as we want to combine the write() calls */
+ i = 1;
+ setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &i, sizeof(i));
+#endif
+
+ if (srv_socket->is_ssl) {
+#ifdef USE_OPENSSL
+ ret = network_write_chunkqueue_openssl(srv, con, cq);
+#endif
+ } else {
+ /* dispatch call */
+#if defined USE_LINUX_SENDFILE
+ ret = network_write_chunkqueue_linuxsendfile(srv, con, cq);
+#elif defined USE_FREEBSD_SENDFILE
+ ret = network_write_chunkqueue_freebsdsendfile(srv, con, cq);
+#elif defined USE_SOLARIS_SENDFILEV
+ ret = network_write_chunkqueue_solarissendfilev(srv, con, cq);
+#elif defined USE_WRITEV
+ ret = network_write_chunkqueue_writev(srv, con, cq);
+#else
+ ret = network_write_chunkqueue_write(srv, con, cq);
+#endif
+ }
+
+ if (ret >= 0) {
+ /*
+ * map the return code
+ *
+ * -1 -> -1
+ * >0 -> (everything written) 0
+ * (not finished yet) 1
+ *
+ * ret means:
+ * - <ret> chunks are unused now
+ *
+ */
+
+ chunk *c, *pc = NULL;
+
+ for (i = 0, c = cq->first; i < ret; i++, c = c->next) {
+ buffer_reset(c->data.mem);
+
+ if (i == ret - 1) pc = c;
+ }
+
+ if (c) {
+ /* there is still something to write */
+
+ if (c != cq->first) {
+ /* move the first few buffers to unused */
+
+ assert(pc);
+
+ pc->next = cq->unused;
+ cq->unused = cq->first;
+ cq->first = c;
+ }
+
+ ret = 1;
+ } else {
+ /* everything is written */
+ chunkqueue_reset(cq);
+
+ ret = 0;
+ }
+ }
+
+#ifdef TCP_CORK
+ i = 0;
+ setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &i, sizeof(i));
+#endif
+
+ written = con->bytes_written - written;
+ con->bytes_written_cur_second += written;
+
+ *(con->conf.global_bytes_per_second_cnt_ptr) += written;
+
+ if (con->conf.kbytes_per_second &&
+ (con->bytes_written_cur_second > con->conf.kbytes_per_second * 1024)) {
+ /* we reached the traffic limit */
+
+ con->traffic_limit_reached = 1;
+ joblist_append(srv, con);
+ }
+ return ret;
+}
diff --git a/src/network.h b/src/network.h
new file mode 100644
index 00000000..99c75966
--- /dev/null
+++ b/src/network.h
@@ -0,0 +1,13 @@
+#ifndef _NETWORK_H_
+#define _NETWORK_H_
+
+#include "server.h"
+
+int network_write_chunkqueue(server *srv, connection *con, chunkqueue *c);
+
+int network_init(server *srv);
+int network_close(server *srv);
+
+int network_register_fdevents(server *srv);
+
+#endif
diff --git a/src/network_backends.h b/src/network_backends.h
new file mode 100644
index 00000000..8238c72b
--- /dev/null
+++ b/src/network_backends.h
@@ -0,0 +1,54 @@
+#ifndef _NETWORK_BACKENDS_H_
+#define _NETWORK_BACKENDS_H_
+
+#include "config.h"
+
+#include <sys/types.h>
+
+/* on linux 2.4.x you get either sendfile or LFS */
+#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) && defined HAVE_WRITEV && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
+# define USE_LINUX_SENDFILE
+# include <sys/sendfile.h>
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_UIO_H && defined HAVE_SENDFILE && defined HAVE_WRITEV && defined(__FreeBSD__)
+# define USE_FREEBSD_SENDFILE
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILEV && defined HAVE_WRITEV && defined(__sun)
+# define USE_SOLARIS_SENDFILEV
+# include <sys/sendfile.h>
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV
+# define USE_WRITEV
+# include <sys/uio.h>
+#endif
+
+#if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP
+# define USE_MMAP
+# include <sys/mman.h>
+/* NetBSD 1.3.x needs it */
+# ifndef MAP_FAILED
+# define MAP_FAILED -1
+# endif
+#endif
+
+#if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV && defined HAVE_SEND_FILE && defined(__aix)
+# define USE_AIX_SENDFILE
+#endif
+
+#include "base.h"
+
+
+int network_write_chunkqueue_write(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_writev(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, chunkqueue *cq);
+int network_write_chunkqueue_openssl(server *srv, connection *con, chunkqueue *cq);
+
+#endif
diff --git a/src/network_freebsd_sendfile.c b/src/network_freebsd_sendfile.c
new file mode 100644
index 00000000..8953fb68
--- /dev/null
+++ b/src/network_freebsd_sendfile.c
@@ -0,0 +1,192 @@
+#include "network_backends.h"
+
+#ifdef USE_FREEBSD_SENDFILE
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+
+#ifndef UIO_MAXIOV
+# ifdef __FreeBSD__
+/* FreeBSD 4.7, 4.9 defined it in sys/uio.h only if _KERNEL is specified */
+# define UIO_MAXIOV 1024
+# endif
+#endif
+
+int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next, chunks_written++) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
+
+ for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ /* check which chunks have been written */
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+ con->bytes_written += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ con->bytes_written += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ off_t offset, r;
+ size_t toSend;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ r = 0;
+
+ /* FreeBSD sendfile() */
+ if (-1 == sendfile(con->fce->fd, fd, offset, toSend, NULL, &r, 0)) {
+ if (errno != EAGAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno);
+
+ return -1;
+ }
+ }
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+ }
+
+ return chunks_written;
+}
+
+#endif
diff --git a/src/network_linux_sendfile.c b/src/network_linux_sendfile.c
new file mode 100644
index 00000000..829c73b9
--- /dev/null
+++ b/src/network_linux_sendfile.c
@@ -0,0 +1,205 @@
+#include "network_backends.h"
+
+#ifdef USE_LINUX_SENDFILE
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+
+int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next, chunks_written++) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for (num_chunks = 0, tc = c;
+ tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV;
+ tc = tc->next, num_chunks++);
+
+ for (tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ /* check which chunks have been written */
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+ con->bytes_written += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ con->bytes_written += r;
+ chunk_finished = 0;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+
+ switch(file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ case HANDLER_GO_ON:
+ offset = c->data.file.offset + c->offset;
+ /* limit the toSend to 2^31-1 bytes in a chunk */
+ toSend = c->data.file.length - c->offset > ((1 << 30) - 1) ?
+ ((1 << 30) - 1) : c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ /* Linux sendfile() */
+ if (-1 == (r = sendfile(fd, con->fce->fd, &offset, toSend))) {
+ if (errno != EAGAIN &&
+ errno != EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile:", strerror(errno), errno);
+
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ break;
+ case HANDLER_WAIT_FOR_FD:
+ /* comeback later */
+
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile (handled):", strerror(errno), errno);
+
+ r = 0;
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+
+ return -1;
+ }
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+ }
+
+ return chunks_written;
+}
+
+#endif
+#if 0
+network_linuxsendfile_init(void) {
+ p->write = network_linuxsendfile_write_chunkset;
+}
+#endif
diff --git a/src/network_openssl.c b/src/network_openssl.c
new file mode 100644
index 00000000..7e8e2f6a
--- /dev/null
+++ b/src/network_openssl.c
@@ -0,0 +1,176 @@
+#include "network_backends.h"
+
+#ifdef USE_OPENSSL
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+
+int network_write_chunkqueue_openssl(server *srv, connection *con, chunkqueue *cq) {
+ int ssl_r;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ if (c->data.mem->used == 0) {
+ chunk_finished = 1;
+ break;
+ }
+
+ offset = c->data.mem->ptr + c->offset;
+ toSend = c->data.mem->used - 1 - c->offset;
+
+ /**
+ * SSL_write man-page
+ *
+ * WARNING
+ * When an SSL_write() operation has to be repeated because of
+ * SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be
+ * repeated with the same arguments.
+ *
+ */
+
+ if ((r = SSL_write(con->ssl, offset, toSend)) <= 0) {
+ switch ((ssl_r = SSL_get_error(con->ssl, r))) {
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(ERR_get_error(), NULL));
+
+ return -1;
+ }
+ } else {
+ c->offset += r;
+ con->bytes_written += r;
+ }
+
+ if (c->offset == (off_t)c->data.mem->used - 1) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ char *s;
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+# if defined USE_MMAP
+ char *p;
+# endif
+
+ WP();
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+
+#if defined USE_MMAP
+ if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ s = p + offset;
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(con->fce->fd, offset, SEEK_SET);
+ if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "read failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ s = srv->tmp_buf->ptr;
+#endif
+
+ if ((r = SSL_write(con->ssl, s, toSend)) <= 0) {
+ switch ((ssl_r = SSL_get_error(con->ssl, r))) {
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:",
+ ssl_r, r,
+ ERR_error_string(ERR_get_error(), NULL));
+
+#if defined USE_MMAP
+ munmap(p, c->data.file.length);
+#endif
+ return -1;
+ }
+ } else {
+ c->offset += r;
+ con->bytes_written += r;
+ }
+
+#if defined USE_MMAP
+ munmap(p, c->data.file.length);
+#endif
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+
+ chunks_written++;
+
+ }
+
+ return chunks_written;
+}
+#endif
+
+#if 0
+network_openssl_init(void) {
+ p->write_ssl = network_openssl_write_chunkset;
+}
+#endif
diff --git a/src/network_solaris_sendfilev.c b/src/network_solaris_sendfilev.c
new file mode 100644
index 00000000..2e0694c1
--- /dev/null
+++ b/src/network_solaris_sendfilev.c
@@ -0,0 +1,208 @@
+#include "network_backends.h"
+
+#ifdef USE_SOLARIS_SENDFILEV
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+#ifndef UIO_MAXIOV
+#define UIO_MAXIOV IOV_MAX
+#endif
+
+/**
+ * a very simple sendfilev() interface for solaris which can be optimised a lot more
+ * as solaris sendfilev() supports 'sending everythin in one syscall()'
+ *
+ * If you want such an interface and need the performance, just give me an account on
+ * a solaris box.
+ * - jan@kneschke.de
+ */
+
+
+int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next, chunks_written++) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
+
+ for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ /* check which chunks have been written */
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+ con->bytes_written += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ con->bytes_written += r;
+ chunk_finished = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "partially write: ", r, fd);
+
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+ ssize_t r;
+ off_t offset;
+ size_t toSend, written;
+ sendfilevec_t fvec;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ fvec.sfv_fd = con->fce->fd;
+ fvec.sfv_flag = 0;
+ fvec.sfv_off = offset;
+ fvec.sfv_len = toSend;
+
+ /* Solaris sendfilev() */
+ if (-1 == (r = sendfilev(fd, &fvec, 1, &written))) {
+ if (errno != EAGAIN) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno);
+
+ return -1;
+ }
+
+ r = 0;
+ }
+
+ c->offset += written;
+ con->bytes_written += written;
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+ }
+
+ return chunks_written;
+}
+
+#endif
diff --git a/src/network_write.c b/src/network_write.c
new file mode 100644
index 00000000..542120b9
--- /dev/null
+++ b/src/network_write.c
@@ -0,0 +1,188 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+#include "sys-socket.h"
+
+#include "network_backends.h"
+
+int network_write_chunkqueue_write(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ if (c->data.mem->used == 0) {
+ chunk_finished = 1;
+ break;
+ }
+
+ offset = c->data.mem->ptr + c->offset;
+ toSend = c->data.mem->used - 1 - c->offset;
+#ifdef __WIN32
+ if ((r = send(fd, offset, toSend, 0)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed: ", strerror(errno), fd);
+
+ return -1;
+ }
+#else
+ if ((r = write(fd, offset, toSend)) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed: ", strerror(errno), fd);
+
+ return -1;
+ }
+#endif
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == (off_t)c->data.mem->used - 1) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+#ifdef USE_MMAP
+ char *p = NULL;
+#endif
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+
+ if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+ if (-1 == con->fce->fd) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "fd is invalid", c->data.file.name);
+
+ return -1;
+ }
+
+#if defined USE_MMAP
+ /* check if the mapping fits */
+ if (con->fce->mmap_p &&
+ con->fce->mmap_length != con->fce->st.st_size &&
+ con->fce->mmap_offset != 0) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+ /* build mapping if neccesary */
+ if (con->fce->mmap_p == NULL) {
+ if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno));
+
+ return -1;
+ }
+ con->fce->mmap_p = p;
+ con->fce->mmap_offset = 0;
+ con->fce->mmap_length = con->fce->st.st_size;
+ } else {
+ p = con->fce->mmap_p;
+ }
+
+ if ((r = write(fd, p + offset, toSend)) <= 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed: ", strerror(errno));
+
+ return -1;
+ }
+
+ /* don't cache mmap()ings for files large then 64k */
+ if (con->fce->mmap_length > 64 * 1024) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(con->fce->fd, offset, SEEK_SET);
+ if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno));
+
+ return -1;
+ }
+#ifdef __WIN32
+ if (-1 == (r = send(fd, srv->tmp_buf->ptr, toSend, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#else
+ if (-1 == (r = write(fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#endif
+#endif
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+
+ chunks_written++;
+ }
+
+ return chunks_written;
+}
+
+#if 0
+network_write_init(void) {
+ p->write = network_write_write_chunkset;
+}
+#endif
diff --git a/src/network_writev.c b/src/network_writev.c
new file mode 100644
index 00000000..3421241c
--- /dev/null
+++ b/src/network_writev.c
@@ -0,0 +1,288 @@
+#include "network_backends.h"
+
+#ifdef USE_WRITEV
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "network.h"
+#include "fdevent.h"
+#include "log.h"
+#include "file_cache.h"
+
+#ifndef UIO_MAXIOV
+# if defined(__FreeBSD__) || defined(__APPLE__) || defined(__NetBSD__)
+/* FreeBSD 4.7 defines it in sys/uio.h only if _KERNEL is specified */
+# define UIO_MAXIOV 1024
+# elif defined(__sgi)
+/* IRIX 6.5 has sysconf(_SC_IOV_MAX) which might return 512 or bigger */
+# define UIO_MAXIOV 512
+# elif defined(__sun)
+/* Solaris (and SunOS?) defines IOV_MAX instead */
+# ifndef IOV_MAX
+# define UIO_MAXIOV 16
+# else
+# define UIO_MAXIOV IOV_MAX
+# endif
+# elif defined(IOV_MAX)
+# define UIO_MAXIOV IOV_MAX
+# else
+# error UIO_MAXIOV nor IOV_MAX are defined
+# endif
+#endif
+
+int network_write_chunkqueue_writev(server *srv, connection *con, chunkqueue *cq) {
+ const int fd = con->fd;
+ chunk *c;
+ size_t chunks_written = 0;
+
+ for(c = cq->first; c; c = c->next) {
+ int chunk_finished = 0;
+
+ switch(c->type) {
+ case MEM_CHUNK: {
+ char * offset;
+ size_t toSend;
+ ssize_t r;
+
+ size_t num_chunks, i;
+ struct iovec chunks[UIO_MAXIOV];
+ chunk *tc;
+ size_t num_bytes = 0;
+
+ /* we can't send more then SSIZE_MAX bytes in one chunk */
+
+ /* build writev list
+ *
+ * 1. limit: num_chunks < UIO_MAXIOV
+ * 2. limit: num_bytes < SSIZE_MAX
+ */
+ for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next);
+
+ for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) {
+ if (tc->data.mem->used == 0) {
+ chunks[i].iov_base = tc->data.mem->ptr;
+ chunks[i].iov_len = 0;
+ } else {
+ offset = tc->data.mem->ptr + tc->offset;
+ toSend = tc->data.mem->used - 1 - tc->offset;
+
+ chunks[i].iov_base = offset;
+
+ /* protect the return value of writev() */
+ if (toSend > SSIZE_MAX ||
+ num_bytes + toSend > SSIZE_MAX) {
+ chunks[i].iov_len = SSIZE_MAX - num_bytes;
+
+ num_chunks = i + 1;
+ break;
+ } else {
+ chunks[i].iov_len = toSend;
+ }
+
+ num_bytes += toSend;
+ }
+ }
+
+ if ((r = writev(fd, chunks, num_chunks)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "writev failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ /* check which chunks have been written */
+
+ for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) {
+ if (r >= (ssize_t)chunks[i].iov_len) {
+ /* written */
+ r -= chunks[i].iov_len;
+ tc->offset += chunks[i].iov_len;
+ con->bytes_written += chunks[i].iov_len;
+
+ if (chunk_finished) {
+ /* skip the chunks from further touches */
+ chunks_written++;
+ c = c->next;
+ } else {
+ /* chunks_written + c = c->next is done in the for()*/
+ chunk_finished++;
+ }
+ } else {
+ /* partially written */
+
+ tc->offset += r;
+ con->bytes_written += r;
+ chunk_finished = 0;
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "(debug) partially write: ", r, fd);
+#endif
+ break;
+ }
+ }
+
+ break;
+ }
+ case FILE_CHUNK: {
+#ifdef USE_MMAP
+ char *p = NULL;
+#endif
+ ssize_t r;
+ off_t offset;
+ size_t toSend;
+
+ switch (file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) {
+ case HANDLER_GO_ON:
+ if (con->fce->st.st_size == 0 ||
+ con->fce->fd == -1) {
+
+ log_error_write(srv, __FILE__, __LINE__, "sbdd", "foo", c->data.file.name,
+ con->fce->st.st_size, con->fce->fd);
+ }
+
+ offset = c->data.file.offset + c->offset;
+ toSend = c->data.file.length - c->offset;
+
+ if (offset > con->fce->st.st_size) {
+ log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name);
+
+ return -1;
+ }
+
+#if defined USE_MMAP
+ /* check if the mapping fits */
+ if (con->fce->mmap_p &&
+ con->fce->mmap_length != con->fce->st.st_size &&
+ con->fce->mmap_offset != 0) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+ /* build mapping if neccesary */
+ if (con->fce->mmap_p == NULL) {
+ if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
+ strerror(errno), c->data.file.name, con->fce->fd);
+
+ return -1;
+ }
+ con->fce->mmap_p = p;
+ con->fce->mmap_offset = 0;
+ con->fce->mmap_length = con->fce->st.st_size;
+ } else {
+ p = con->fce->mmap_p;
+ }
+
+ if ((r = write(fd, p + offset, toSend)) <= 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), fd);
+
+ return -1;
+ }
+ }
+
+ /* don't cache mmap()ings for files large then 64k */
+ if (con->fce->mmap_length > 64 * 1024) {
+ munmap(con->fce->mmap_p, con->fce->mmap_length);
+
+ con->fce->mmap_p = NULL;
+ }
+
+#else
+ buffer_prepare_copy(srv->tmp_buf, toSend);
+
+ lseek(con->fce->fd, offset, SEEK_SET);
+ if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "read:", strerror(errno));
+
+ return -1;
+ }
+
+ if (-1 == (r = write(fd, srv->tmp_buf->ptr, toSend))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno));
+
+ return -1;
+ }
+#endif
+
+
+ break;
+ case HANDLER_WAIT_FOR_FD:
+
+ log_error_write(srv, __FILE__, __LINE__, "ssd", "writev (handled):", strerror(errno), errno);
+
+ r = 0;
+
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ strerror(errno), c->data.file.name);
+ return -1;
+ }
+
+ c->offset += r;
+ con->bytes_written += r;
+
+ if (c->offset == c->data.file.length) {
+ chunk_finished = 1;
+ }
+
+ break;
+ }
+ default:
+
+ log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known");
+
+ return -1;
+ }
+
+ if (!chunk_finished) {
+ /* not finished yet */
+
+ break;
+ }
+
+ chunks_written++;
+ }
+
+ return chunks_written;
+}
+
+#endif
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 00000000..c16781b8
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,433 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include <stdio.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "config.h"
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#ifndef __WIN32
+#include <dlfcn.h>
+#endif
+/*
+ *
+ * if you change this enum to add a new callback, be sure
+ * - that PLUGIN_FUNC_SIZEOF is the last entry
+ * - that you add PLUGIN_TO_SLOT twice:
+ * 1. as callback-dispatcher
+ * 2. in plugins_call_init()
+ *
+ */
+
+typedef struct {
+ PLUGIN_DATA;
+} plugin_data;
+
+typedef enum {
+ PLUGIN_FUNC_UNSET,
+ PLUGIN_FUNC_HANDLE_URI_CLEAN,
+ PLUGIN_FUNC_HANDLE_URI_RAW,
+ PLUGIN_FUNC_HANDLE_PHYSICAL_PATH,
+ PLUGIN_FUNC_HANDLE_REQUEST_DONE,
+ PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,
+ PLUGIN_FUNC_HANDLE_TRIGGER,
+ PLUGIN_FUNC_HANDLE_SIGHUP,
+ PLUGIN_FUNC_HANDLE_SUBREQUEST,
+ PLUGIN_FUNC_HANDLE_SUBREQUEST_START,
+ PLUGIN_FUNC_HANDLE_JOBLIST,
+ PLUGIN_FUNC_HANDLE_DOCROOT,
+ PLUGIN_FUNC_CONNECTION_RESET,
+ PLUGIN_FUNC_INIT,
+ PLUGIN_FUNC_CLEANUP,
+ PLUGIN_FUNC_SET_DEFAULTS,
+
+ PLUGIN_FUNC_SIZEOF
+} plugin_t;
+
+static plugin *plugin_init(void) {
+ plugin *p;
+
+ p = calloc(1, sizeof(*p));
+
+ return p;
+}
+
+static void plugin_free(plugin *p) {
+ int use_dlclose = 1;
+ if (p->name) buffer_free(p->name);
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ if (RUNNING_ON_VALGRIND) use_dlclose = 0;
+#endif
+
+ if (use_dlclose && p->lib) {
+#ifdef __WIN32
+ FreeLibrary(p->lib);
+#else
+ dlclose(p->lib);
+#endif
+ }
+
+ free(p);
+}
+
+static int plugins_register(server *srv, plugin *p) {
+ plugin **ps;
+ if (0 == srv->plugins.size) {
+ srv->plugins.size = 4;
+ srv->plugins.ptr = malloc(srv->plugins.size * sizeof(*ps));
+ srv->plugins.used = 0;
+ } else if (srv->plugins.used == srv->plugins.size) {
+ srv->plugins.size += 4;
+ srv->plugins.ptr = realloc(srv->plugins.ptr, srv->plugins.size * sizeof(*ps));
+ }
+
+ ps = srv->plugins.ptr;
+ ps[srv->plugins.used++] = p;
+
+ return 0;
+}
+
+/**
+ *
+ *
+ *
+ */
+
+
+int plugins_load(server *srv) {
+ plugin *p;
+ int (*init)(plugin *pl);
+ const char *error;
+ size_t i;
+
+ for (i = 0; i < srv->srvconf.modules->used; i++) {
+ data_string *d = (data_string *)srv->srvconf.modules->data[i];
+ char *modules = d->value->ptr;
+ char *inst;
+
+ buffer_reset(srv->tmp_buf);
+ /* for make distcheck */
+ if (NULL != (inst = strstr(LIBRARY_DIR, "/_inst/lib"))) {
+ buffer_copy_string_len(srv->tmp_buf, LIBRARY_DIR, strlen(LIBRARY_DIR) - strlen(inst));
+ buffer_append_string(srv->tmp_buf, "/_build/src/.libs");
+ buffer_append_string(srv->tmp_buf, inst + strlen("/_inst/lib") );
+ } else {
+ buffer_copy_string(srv->tmp_buf, LIBRARY_DIR);
+ }
+ buffer_append_string(srv->tmp_buf, "/");
+ buffer_append_string(srv->tmp_buf, modules);
+#if defined(__WIN32) || defined(__CYGWIN__)
+ buffer_append_string(srv->tmp_buf, ".dll");
+#else
+ buffer_append_string(srv->tmp_buf, ".so");
+#endif
+
+ p = plugin_init();
+#ifdef __WIN32
+ if (NULL == (p->lib = LoadLibrary(srv->tmp_buf->ptr))) {
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "LoadLibrary() failed",
+ lpMsgBuf, srv->tmp_buf);
+
+ plugin_free(p);
+
+ return -1;
+
+ }
+#else
+ if (NULL == (p->lib = dlopen(srv->tmp_buf->ptr, RTLD_LAZY))) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "dlopen() failed",
+ dlerror());
+
+ plugin_free(p);
+
+ return -1;
+ }
+
+#endif
+ buffer_reset(srv->tmp_buf);
+ buffer_copy_string(srv->tmp_buf, modules);
+ buffer_append_string(srv->tmp_buf, "_plugin_init");
+
+#ifdef __WIN32
+ init = GetProcAddress(p->lib, srv->tmp_buf->ptr);
+
+ if (init == NULL) {
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ log_error_write(srv, __FILE__, __LINE__, "sbs", "getprocaddress failed:", srv->tmp_buf, lpMsgBuf);
+
+ plugin_free(p);
+ return -1;
+ }
+
+#else
+ *(void **)(&init) = dlsym(p->lib, srv->tmp_buf->ptr);
+ if ((error = dlerror()) != NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "s", error);
+
+ plugin_free(p);
+ return -1;
+ }
+
+#endif
+ if ((*init)(p)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed" );
+
+ plugin_free(p);
+ return -1;
+ }
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin loaded" );
+#endif
+ plugins_register(srv, p);
+ }
+
+ return 0;
+}
+
+#define PLUGIN_TO_SLOT(x, y) \
+ handler_t plugins_call_##y(server *srv, connection *con) {\
+ plugin **slot;\
+ size_t j;\
+ if (!srv->plugin_slots) return HANDLER_GO_ON;\
+ slot = ((plugin ***)(srv->plugin_slots))[x];\
+ if (!slot) return HANDLER_GO_ON;\
+ for (j = 0; j < srv->plugins.used && slot[j]; j++) { \
+ plugin *p = slot[j];\
+ handler_t r;\
+ switch(r = p->y(srv, con, p->data)) {\
+ case HANDLER_GO_ON:\
+ break;\
+ case HANDLER_FINISHED:\
+ case HANDLER_COMEBACK:\
+ case HANDLER_WAIT_FOR_EVENT:\
+ case HANDLER_WAIT_FOR_FD:\
+ case HANDLER_ERROR:\
+ return r;\
+ default:\
+ log_error_write(srv, __FILE__, __LINE__, "sbs", #x, p->name, "unknown state");\
+ return HANDLER_ERROR;\
+ }\
+ }\
+ return HANDLER_GO_ON;\
+ }
+
+/**
+ * plugins that use
+ *
+ * - server *srv
+ * - connection *con
+ * - void *p_d (plugin_data *)
+ */
+
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL_PATH, handle_physical_path)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset)
+
+#undef PLUGIN_TO_SLOT
+
+#define PLUGIN_TO_SLOT(x, y) \
+ handler_t plugins_call_##y(server *srv) {\
+ plugin **slot;\
+ size_t j;\
+ if (!srv->plugin_slots) return HANDLER_GO_ON;\
+ slot = ((plugin ***)(srv->plugin_slots))[x];\
+ if (!slot) return HANDLER_GO_ON;\
+ for (j = 0; j < srv->plugins.used && slot[j]; j++) { \
+ plugin *p = slot[j];\
+ handler_t r;\
+ switch(r = p->y(srv, p->data)) {\
+ case HANDLER_GO_ON:\
+ break;\
+ case HANDLER_FINISHED:\
+ case HANDLER_COMEBACK:\
+ case HANDLER_WAIT_FOR_EVENT:\
+ case HANDLER_WAIT_FOR_FD:\
+ case HANDLER_ERROR:\
+ return r;\
+ default:\
+ log_error_write(srv, __FILE__, __LINE__, "sbsd", #x, p->name, "unknown state:", r);\
+ return HANDLER_ERROR;\
+ }\
+ }\
+ return HANDLER_GO_ON;\
+ }
+
+/**
+ * plugins that use
+ *
+ * - server *srv
+ * - void *p_d (plugin_data *)
+ */
+
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup)
+PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults)
+
+#undef PLUGIN_TO_SLOT
+
+#if 0
+/**
+ *
+ * special handler
+ *
+ */
+handler_t plugins_call_handle_fdevent(server *srv, const fd_conn *fdc) {
+ size_t i;
+ plugin **ps;
+
+ ps = srv->plugins.ptr;
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin *p = ps[i];
+ if (p->handle_fdevent) {
+ handler_t r;
+ switch(r = p->handle_fdevent(srv, fdc, p->data)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+#endif
+/**
+ *
+ * - call init function of all plugins to init the plugin-internals
+ * - added each plugin that supports has callback to the corresponding slot
+ *
+ * - is only called once.
+ */
+
+handler_t plugins_call_init(server *srv) {
+ size_t i;
+ plugin **ps;
+
+ ps = srv->plugins.ptr;
+
+ /* fill slots */
+
+ srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ size_t j;
+ /* check which calls are supported */
+
+ plugin *p = ps[i];
+
+#define PLUGIN_TO_SLOT(x, y) \
+ if (p->y) { \
+ plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
+ if (!slot) { \
+ slot = calloc(srv->plugins.used, sizeof(*slot));\
+ ((plugin ***)(srv->plugin_slots))[x] = slot; \
+ } \
+ for (j = 0; j < srv->plugins.used; j++) { \
+ if (slot[j]) continue;\
+ slot[j] = p;\
+ break;\
+ }\
+ }
+
+
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL_PATH, handle_physical_path);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup);
+ PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
+#undef PLUGIN_TO_SLOT
+
+ if (p->init) {
+ if (NULL == (p->data = p->init())) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "plugin-init failed for module", p->name);
+ return HANDLER_ERROR;
+ }
+
+ /* used for con->mode, DIRECT == 0, plugins above that */
+ ((plugin_data *)(p->data))->id = i + 1;
+
+ if (p->version != LIGHTTPD_VERSION_ID) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "plugin-version doesn't match lighttpd-version for", p->name);
+ return HANDLER_ERROR;
+ }
+ } else {
+ p->data = NULL;
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+void plugins_free(server *srv) {
+ size_t i;
+ plugins_call_cleanup(srv);
+
+ for (i = 0; i < srv->plugins.used; i++) {
+ plugin *p = ((plugin **)srv->plugins.ptr)[i];
+
+ plugin_free(p);
+ }
+
+ for (i = 0; srv->plugin_slots && i < PLUGIN_FUNC_SIZEOF; i++) {
+ plugin **slot = ((plugin ***)(srv->plugin_slots))[i];
+
+ if (slot) free(slot);
+ }
+
+ free(srv->plugin_slots);
+ srv->plugin_slots = NULL;
+
+ free(srv->plugins.ptr);
+ srv->plugins.ptr = NULL;
+}
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644
index 00000000..6d33a921
--- /dev/null
+++ b/src/plugin.h
@@ -0,0 +1,91 @@
+#ifndef _PLUGIN_H_
+#define _PLUGIN_H_
+
+#include "base.h"
+#include "buffer.h"
+
+#define SERVER_FUNC(x) \
+ static handler_t x(server *srv, void *p_d)
+
+#define CONNECTION_FUNC(x) \
+ static handler_t x(server *srv, connection *con, void *p_d)
+
+#define INIT_FUNC(x) \
+ static void *x()
+
+#define FREE_FUNC SERVER_FUNC
+#define TRIGGER_FUNC SERVER_FUNC
+#define SETDEFAULTS_FUNC SERVER_FUNC
+#define SIGHUP_FUNC SERVER_FUNC
+
+#define SUBREQUEST_FUNC CONNECTION_FUNC
+#define JOBLIST_FUNC CONNECTION_FUNC
+#define PHYSICALPATH_FUNC CONNECTION_FUNC
+#define REQUESTDONE_FUNC CONNECTION_FUNC
+#define URIHANDLER_FUNC CONNECTION_FUNC
+
+#define PLUGIN_DATA size_t id
+
+typedef struct {
+ size_t version;
+
+ buffer *name; /* name of the plugin */
+
+ void *(* init) ();
+ handler_t (* set_defaults) (server *srv, void *p_d);
+ handler_t (* cleanup) (server *srv, void *p_d);
+ /* is called ... */
+ handler_t (* handle_trigger) (server *srv, void *p_d); /* once a second */
+ handler_t (* handle_sighup) (server *srv, void *p_d); /* at a signup */
+
+ handler_t (* handle_uri_raw) (server *srv, connection *con, void *p_d); /* after uri_raw is set */
+ handler_t (* handle_uri_clean) (server *srv, connection *con, void *p_d); /* after uri is set */
+ handler_t (* handle_docroot) (server *srv, connection *con, void *p_d); /* getting the document-root */
+ handler_t (* handle_physical_path) (server *srv, connection *con, void *p_d); /* after the physical path is set */
+ handler_t (* handle_request_done) (server *srv, connection *con, void *p_d); /* at the end of a request */
+ handler_t (* handle_connection_close)(server *srv, connection *con, void *p_d); /* at the end of a connection */
+ handler_t (* handle_joblist) (server *srv, connection *con, void *p_d); /* after all events are handled */
+
+
+
+ handler_t (* handle_subrequest_start)(server *srv, connection *con, void *p_d);
+
+ /* when a handler for the request
+ * has to be found
+ */
+ handler_t (* handle_subrequest) (server *srv, connection *con, void *p_d); /* */
+ handler_t (* connection_reset) (server *srv, connection *con, void *p_d); /* */
+ void *data;
+
+ /* dlopen handle */
+ void *lib;
+} plugin;
+
+int plugins_load(server *srv);
+void plugins_free(server *srv);
+
+handler_t plugins_call_handle_uri_raw(server *srv, connection *con);
+handler_t plugins_call_handle_uri_clean(server *srv, connection *con);
+handler_t plugins_call_handle_subrequest_start(server *srv, connection *con);
+handler_t plugins_call_handle_subrequest(server *srv, connection *con);
+handler_t plugins_call_handle_request_done(server *srv, connection *con);
+handler_t plugins_call_handle_docroot(server *srv, connection *con);
+handler_t plugins_call_handle_connection_close(server *srv, connection *con);
+handler_t plugins_call_handle_joblist(server *srv, connection *con);
+handler_t plugins_call_handle_physical_path(server *srv, connection *con);
+handler_t plugins_call_connection_reset(server *srv, connection *con);
+
+handler_t plugins_call_handle_trigger(server *srv);
+handler_t plugins_call_handle_sighup(server *srv);
+
+handler_t plugins_call_init(server *srv);
+handler_t plugins_call_set_defaults(server *srv);
+handler_t plugins_call_cleanup(server *srv);
+
+int config_insert_values_global(server *srv, array *ca, const config_values_t *cv);
+int config_insert_values_internal(server *srv, array *ca, const config_values_t *cv);
+int config_setup_connection(server *srv, connection *con);
+int config_patch_connection(server *srv, connection *con, const char *stage, size_t stage_len);
+int config_check_cond(server *srv, connection *con, data_config *dc);
+
+#endif
diff --git a/src/request.c b/src/request.c
new file mode 100644
index 00000000..bbffd1c9
--- /dev/null
+++ b/src/request.c
@@ -0,0 +1,937 @@
+#include <sys/stat.h>
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "request.h"
+#include "keyvalue.h"
+#include "log.h"
+
+static int request_check_hostname(server *srv, connection *con, buffer *host) {
+ enum { DOMAINLABEL, TOPLABEL } stage = TOPLABEL;
+ size_t i;
+ int label_len = 0;
+ size_t host_len;
+ char *colon;
+ int is_ip = -1; /* -1 don't know yet, 0 no, 1 yes */
+ int level = 0;
+
+ UNUSED(srv);
+ UNUSED(con);
+
+ /*
+ * hostport = host [ ":" port ]
+ * host = hostname | IPv4address | IPv6address
+ * hostname = *( domainlabel "." ) toplabel [ "." ]
+ * domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ * toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+ * IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+ * IPv6address = "[" ... "]"
+ * port = *digit
+ */
+
+ /* no Host: */
+ if (!host || host->used == 0) return 0;
+
+ host_len = host->used - 1;
+
+ /* IPv6 adress */
+ if (host->ptr[0] == '[') {
+ char *c = host->ptr + 1;
+ int colon_cnt = 0;
+
+ /* check portnumber */
+ for (; *c && *c != ']'; c++) {
+ if (*c == ':') {
+ if (++colon_cnt > 7) {
+ return -1;
+ }
+ } else if (!light_isxdigit(*c)) {
+ return -1;
+ }
+ }
+
+ /* missing ] */
+ if (!*c) {
+ return -1;
+ }
+
+ /* check port */
+ if (*(c+1) == ':') {
+ for (c += 2; *c; c++) {
+ if (!light_isdigit(*c)) {
+ return -1;
+ }
+ }
+ }
+ return 0;
+ }
+
+ if (NULL != (colon = memchr(host->ptr, ':', host_len))) {
+ char *c = colon + 1;
+
+ /* check portnumber */
+ for (; *c; c++) {
+ if (!light_isdigit(*c)) return -1;
+ }
+
+ /* remove the port from the host-len */
+ host_len = colon - host->ptr;
+ }
+
+ /* Host is empty */
+ if (host_len == 0) return -1;
+
+ /* scan from the right and skip the \0 */
+ for (i = host_len - 1; i + 1 > 0; i--) {
+ char c = host->ptr[i];
+
+ switch (stage) {
+ case TOPLABEL:
+ if (c == '.') {
+ /* only switch stage, if this is not the last character */
+ if (i != host_len - 1) {
+ if (label_len == 0) {
+ return -1;
+ }
+
+ /* check the first character at right of the dot */
+ if (is_ip == 0) {
+ if (!light_isalpha(host->ptr[i+1])) {
+ return -1;
+ }
+ } else if (!light_isdigit(host->ptr[i+1])) {
+ is_ip = 0;
+ } else if ('-' == host->ptr[i+1]) {
+ return -1;
+ } else {
+ /* just digits */
+ is_ip = 1;
+ }
+
+ stage = DOMAINLABEL;
+
+ label_len = 0;
+ level++;
+ } else if (i == 0) {
+ /* just a dot and nothing else is evil */
+ return -1;
+ }
+ } else if (i == 0) {
+ /* the first character of the hostname */
+ if (!light_isalpha(c)) {
+ return -1;
+ }
+ label_len++;
+ } else {
+ if (c != '-' && !light_isalnum(c)) {
+ return -1;
+ }
+ if (is_ip == -1) {
+ if (!light_isdigit(c)) is_ip = 0;
+ }
+ label_len++;
+ }
+
+ break;
+ case DOMAINLABEL:
+ if (is_ip == 1) {
+ if (c == '.') {
+ if (label_len == 0) {
+ return -1;
+ }
+
+ label_len = 0;
+ level++;
+ } else if (!light_isdigit(c)) {
+ return -1;
+ } else {
+ label_len++;
+ }
+ } else {
+ if (c == '.') {
+ if (label_len == 0) {
+ return -1;
+ }
+
+ /* c is either - or alphanum here */
+ if ('-' == host->ptr[i+1]) {
+ return -1;
+ }
+
+ label_len = 0;
+ level++;
+ } else if (i == 0) {
+ if (!light_isalnum(c)) {
+ return -1;
+ }
+ label_len++;
+ } else {
+ if (c != '-' && !light_isalnum(c)) {
+ return -1;
+ }
+ label_len++;
+ }
+ }
+
+ break;
+ }
+ }
+
+ /* a IP has to consist of 4 parts */
+ if (is_ip == 1 && level != 3) {
+ return -1;
+ }
+
+ if (label_len == 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#if 0
+#define DUMP_HEADER
+#endif
+
+int http_request_split_value(array *vals, buffer *b) {
+ char *s;
+ size_t i;
+ int state = 0;
+ /*
+ * parse
+ *
+ * val1, val2, val3, val4
+ *
+ * into a array (more or less a explode() incl. striping of whitespaces
+ */
+
+ if (b->used == 0) return 0;
+
+ s = b->ptr;
+
+ for (i =0; i < b->used - 1; ) {
+ char *start = NULL, *end = NULL;
+ data_string *ds;
+
+ switch (state) {
+ case 0: /* ws */
+
+ /* skip ws */
+ for (; (*s == ' ' || *s == '\t') && i < b->used - 1; i++, s++);
+
+
+ state = 1;
+ break;
+ case 1: /* value */
+ start = s;
+
+ for (; *s != ',' && i < b->used - 1; i++, s++);
+ end = s - 1;
+
+ for (; (*end == ' ' || *end == '\t') && end > start; end--);
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(vals, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+
+ buffer_copy_string_len(ds->value, start, end-start+1);
+ array_insert_unique(vals, (data_unset *)ds);
+
+ if (*s == ',') {
+ state = 0;
+ i++;
+ s++;
+ } else {
+ /* end of string */
+
+ state = 2;
+ }
+ break;
+ default:
+ i++;
+ break;
+ }
+ }
+ return 0;
+}
+
+int request_uri_is_valid_char(char c) {
+ /* RFC 2396 - Appendix A */
+
+ /* alphanum */
+ if (light_isalnum(c)) return 1;
+
+
+ switch(c) {
+ /* reserved */
+ case ';':
+ case '/':
+ case '?':
+ case ':':
+ case '@':
+ case '&':
+ case '=':
+ case '+':
+ case '$':
+ case ',':
+
+ /* mark */
+ case '-':
+ case '_':
+ case '.':
+ case '!':
+ case '~':
+ case '*':
+ case '\'':
+ case '(':
+ case ')':
+
+ /* escaped */
+ case '%':
+
+ /* fragment, should not be out in the wild $*/
+ case '#':
+
+ /* non RFC */
+ case '[':
+ case ']':
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int http_request_parse(server *srv, connection *con) {
+ char *uri = NULL, *proto = NULL, *method = NULL, con_length_set;
+ int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding;
+ char *value = NULL, *key = NULL;
+
+ enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET;
+
+ int line = 0;
+
+ int request_line_stage = 0;
+ size_t i, first;
+
+ int done = 0;
+
+ data_string *ds = NULL;
+
+ /*
+ * Request: "^(GET|POST|HEAD) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$"
+ * Option : "^([-a-zA-Z]+): (.+)$"
+ * End : "^$"
+ */
+
+ if (con->conf.log_request_header) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsdSb",
+ "fd:", con->fd,
+ "request-len:", con->request.request->used,
+ "\n", con->request.request);
+ }
+
+ if (con->request_count > 1 &&
+ con->request.request->ptr[0] == '\r' &&
+ con->request.request->ptr[1] == '\n') {
+ /* we are in keep-alive and might get \r\n after a previous POST request.*/
+
+ buffer_copy_string_len(con->parse_request, con->request.request->ptr + 2, con->request.request->used - 1 - 2);
+ } else {
+ /* fill the local request buffer */
+ buffer_copy_string_buffer(con->parse_request, con->request.request);
+ }
+
+ keep_alive_set = 0;
+ con_length_set = 0;
+
+ /* parse the first line of the request
+ *
+ * should be:
+ *
+ * <method> <uri> <protocol>\r\n
+ * */
+ for (i = 0, first = 0; i < con->parse_request->used && line == 0; i++) {
+ char *cur = con->parse_request->ptr + i;
+
+ switch(*cur) {
+ case '\r':
+ if (con->parse_request->ptr[i+1] == '\n') {
+ http_method_t r;
+ char *nuri = NULL;
+ size_t j;
+
+ /* \r\n -> \0\0 */
+ con->parse_request->ptr[i] = '\0';
+ con->parse_request->ptr[i+1] = '\0';
+
+ buffer_copy_string_len(con->request.request_line, con->parse_request->ptr, i);
+
+ if (request_line_stage != 2) {
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ proto = con->parse_request->ptr + first;
+
+ *(uri - 1) = '\0';
+ *(proto - 1) = '\0';
+
+ /* we got the first one :) */
+ if (-1 == (r = get_http_method_key(method))) {
+ con->http_status = 501;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ return 0;
+ }
+
+ con->request.http_method = r;
+
+ if (0 == strncmp(proto, "HTTP/1.", sizeof("HTTP/1.") - 1)) {
+ if (proto[7] == '1') {
+ con->request.http_version = con->conf.allow_http11 ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0;
+ } else if (proto[7] == '0') {
+ con->request.http_version = HTTP_VERSION_1_0;
+ } else {
+ con->http_status = 505;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown HTTP version -> 505");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+ } else {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ if (0 == strncmp(uri, "http://", 7) &&
+ NULL != (nuri = strchr(uri + 7, '/'))) {
+ /* ignore the host-part */
+
+ buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1);
+ } else {
+ /* everything looks good so far */
+ buffer_copy_string_len(con->request.uri, uri, proto - uri - 1);
+ }
+
+ /* check uri for invalid characters */
+ for (j = 0; j < con->request.uri->used - 1; j++) {
+ if (!request_uri_is_valid_char(con->request.uri->ptr[j])) {
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "invalid character in URI -> 400",
+ con->request.uri->ptr[j]);
+
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+
+ return 0;
+ }
+ }
+
+ buffer_copy_string_buffer(con->request.orig_uri, con->request.uri);
+
+ con->http_status = 0;
+
+ i++;
+ line++;
+ first = i+1;
+ }
+ break;
+ case ' ':
+ switch(request_line_stage) {
+ case 0:
+ /* GET|POST|... */
+ method = con->parse_request->ptr + first;
+ first = i + 1;
+ break;
+ case 1:
+ /* /foobar/... */
+ uri = con->parse_request->ptr + first;
+ first = i + 1;
+ break;
+ default:
+ /* ERROR, one space to much */
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400");
+ if (srv->srvconf.log_request_header_on_error) {
+ log_error_write(srv, __FILE__, __LINE__, "Sb",
+ "request-header:\n",
+ con->request.request);
+ }
+ return 0;
+ }
+
+ request_line_stage++;
+ break;
+ }
+ }
+
+ in_folding = 0;
+
+ for (; i < con->parse_request->used && !done; i++) {
+ char *cur = con->parse_request->ptr + i;
+
+ if (is_key) {
+ size_t j;
+ int got_colon = 0;
+
+ /**
+ * 1*<any CHAR except CTLs or separators>
+ * CTLs == 0-31 + 127
+ *
+ */
+ switch(*cur) {
+ case ':':
+ is_key = 0;
+
+ value = cur + 1;
+
+ if (is_ws_after_key == 0) {
+ key_len = i - first;
+ }
+ is_ws_after_key = 0;
+
+ break;
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '@':
+ case ',':
+ case ';':
+ case '\\':
+ case '\"':
+ case '/':
+ case '[':
+ case ']':
+ case '?':
+ case '=':
+ case '{':
+ case '}':
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsds",
+ "invalid character in key", con->request.request, cur, *cur, "-> 400");
+ return 0;
+ case ' ':
+ case '\t':
+ if (i == first) {
+ is_key = 0;
+ in_folding = 1;
+ value = cur;
+
+ break;
+ }
+
+
+ key_len = i - first;
+
+ /* skip every thing up to the : */
+ for (j = 1; !got_colon; j++) {
+ switch(con->parse_request->ptr[j + i]) {
+ case ' ':
+ case '\t':
+ /* skip WS */
+ continue;
+ case ':':
+ /* ok, done */
+
+ i += j - 1;
+ got_colon = 1;
+
+ break;
+ default:
+ /* error */
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "WS character in key -> 400");
+
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ return 0;
+ }
+ }
+
+ break;
+ case '\r':
+ if (con->parse_request->ptr[i+1] == '\n' && i == first) {
+ /* End of Header */
+ con->parse_request->ptr[i] = '\0';
+ con->parse_request->ptr[i+1] = '\0';
+
+ i++;
+
+ done = 1;
+
+ break;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s", "CR without LF -> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+ return 0;
+ }
+ /* fall thru */
+ case 0: /* illegal characters (faster than a if () :) */
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 10:
+ case 11:
+ case 12:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ case 127:
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "sbsds",
+ "CTL character in key", con->request.request, cur, *cur, "-> 400");
+
+ return 0;
+ default:
+ /* ok */
+ break;
+ }
+ } else {
+ switch(*cur) {
+ case '\r':
+ if (con->parse_request->ptr[i+1] == '\n') {
+ /* End of Headerline */
+ con->parse_request->ptr[i] = '\0';
+ con->parse_request->ptr[i+1] = '\0';
+
+ if (in_folding) {
+ if (!ds) {
+ /* 400 */
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "WS at the start of first line -> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+ return 0;
+ }
+ buffer_append_string(ds->value, value);
+ } else {
+ int s_len;
+ key = con->parse_request->ptr + first;
+
+ s_len = cur - value;
+
+ if (s_len > 0) {
+ int cmp = 0;
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+ ds = data_string_init();
+ }
+ buffer_copy_string_len(ds->key, key, key_len);
+ buffer_copy_string_len(ds->value, value, s_len);
+
+ /* retreive values
+ *
+ *
+ * the list of options is sorted to simplify the search
+ */
+
+ if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) {
+ array *vals;
+ size_t vi;
+
+ /* split on , */
+
+ vals = srv->split_vals;
+
+ array_reset(vals);
+
+ http_request_split_value(vals, ds->value);
+
+ for (vi = 0; vi < vals->used; vi++) {
+ data_string *dsv = (data_string *)vals->data[vi];
+
+ if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) {
+ keep_alive_set = HTTP_CONNECTION_KEEPALIVE;
+
+ break;
+ } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) {
+ keep_alive_set = HTTP_CONNECTION_CLOSE;
+
+ break;
+ }
+ }
+
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) {
+ char *err;
+ unsigned long int r;
+ size_t j;
+
+ if (ds->value->used == 0) SEGFAULT();
+
+ for (j = 0; j < ds->value->used - 1; j++) {
+ char c = ds->value->ptr[j];
+ if (!isdigit((unsigned char)c)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "content-length broken:", ds->value, "-> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ return 0;
+ }
+ }
+
+ r = strtoul(ds->value->ptr, &err, 10);
+
+ if (*err == '\0') {
+ con_length_set = 1;
+ con->request.content_length = r;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "content-length broken:", ds->value, "-> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ return 0;
+ }
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) {
+ con->request.http_content_type = ds->value->ptr;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Expect")))) {
+ /* HTTP 2616 8.2.3
+ * Expect: 100-continue
+ *
+ * -> (10.1.1) 100 (read content, process request, send final status-code)
+ * -> (10.4.18) 417 (close)
+ *
+ * (not handled at all yet, we always send 417 here)
+ */
+
+ con->http_status = 417;
+ con->keep_alive = 0;
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ return 0;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) {
+ con->request.http_host = ds->value;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) {
+ con->request.http_if_modified_since = ds->value->ptr;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) {
+ con->request.http_if_none_match = ds->value->ptr;
+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Range")))) {
+ /* bytes=.*-.* */
+
+ if (0 == strncasecmp(ds->value->ptr, "bytes=", 6) &&
+ NULL != strchr(ds->value->ptr+6, '-')) {
+
+ con->request.http_range = ds->value->ptr + 6;
+ }
+ }
+
+ array_insert_unique(con->request.headers, (data_unset *)ds);
+ } else {
+ /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
+ }
+ }
+
+ i++;
+ first = i+1;
+ is_key = 1;
+ value = 0;
+ key_len = 0;
+ in_folding = 0;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "CR without LF", con->request.request, "-> 400");
+
+ con->http_status = 400;
+ con->keep_alive = 0;
+ con->response.keep_alive = 0;
+ return 0;
+ }
+ break;
+ case ' ':
+ case '\t':
+ /* strip leading WS */
+ if (value == cur) value = cur+1;
+ default:
+ break;
+ }
+ }
+ }
+
+ con->header_len = i;
+
+ /* do some post-processing */
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ if (keep_alive_set != HTTP_CONNECTION_CLOSE) {
+ /* no Connection-Header sent */
+
+ /* HTTP/1.1 -> keep-alive default TRUE */
+ con->keep_alive = 1;
+ } else {
+ con->keep_alive = 0;
+ }
+
+ /* RFC 2616, 14.23 */
+ if (con->request.http_host == NULL ||
+ buffer_is_empty(con->request.http_host)) {
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400");
+ return 0;
+ }
+ } else {
+ if (keep_alive_set == HTTP_CONNECTION_KEEPALIVE) {
+ /* no Connection-Header sent */
+
+ /* HTTP/1.0 -> keep-alive default FALSE */
+ con->keep_alive = 1;
+ } else {
+ con->keep_alive = 0;
+ }
+ }
+
+ /* check hostname field */
+ if (0 != request_check_hostname(srv, con, con->request.http_host)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "Invalid Hostname:", con->request.http_host, "-> 400");
+
+ con->http_status = 400;
+ con->response.keep_alive = 0;
+ con->keep_alive = 0;
+
+ return 0;
+ }
+
+ /* check if we have read post data */
+ if (con->request.http_method == HTTP_METHOD_POST) {
+ server_socket *srv_socket = con->srv_socket;
+ if (con->request.http_content_type == NULL) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "POST request, but content-type not set");
+ }
+
+ if (con_length_set == 0) {
+ /* content-length is missing */
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "POST-request, but content-length missing -> 411");
+
+ con->http_status = 411;
+ return 0;
+ }
+
+ /* don't handle more the SSIZE_MAX bytes in content-length */
+ if (con->request.content_length > SSIZE_MAX) {
+ con->http_status = 413;
+
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "request-size too long:", con->request.content_length, "-> 413");
+ return 0;
+ }
+
+ /* divide by 1024 as srvconf.max_request_size is in kBytes */
+ if (srv_socket->max_request_size != 0 &&
+ (con->request.content_length >> 10) > srv_socket->max_request_size) {
+ /* the request body itself is larger then
+ * our our max_request_size
+ */
+
+ con->http_status = 413;
+
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "request-size too long:", con->request.content_length, "-> 413");
+ return 0;
+ }
+
+
+ /* we have content */
+ if (con->request.content_length != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int http_request_header_finished(server *srv, connection *con) {
+ UNUSED(srv);
+
+ if (con->request.request->used < 5) return 0;
+
+ if (0 == memcmp(con->request.request->ptr + con->request.request->used - 5, "\r\n\r\n", 4)) return 1;
+ if (NULL != strstr(con->request.request->ptr, "\r\n\r\n")) return 1;
+
+ return 0;
+}
diff --git a/src/request.h b/src/request.h
new file mode 100644
index 00000000..cf2b07d4
--- /dev/null
+++ b/src/request.h
@@ -0,0 +1,9 @@
+#ifndef _REQUEST_H_
+#define _REQUEST_H_
+
+#include "server.h"
+
+int http_request_parse(server *srv, connection *con);
+int http_request_header_finished(server *srv, connection *con);
+
+#endif
diff --git a/src/response.c b/src/response.c
new file mode 100644
index 00000000..1f67d70e
--- /dev/null
+++ b/src/response.c
@@ -0,0 +1,1432 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <stdio.h>
+
+#include "response.h"
+#include "keyvalue.h"
+#include "log.h"
+#include "file_cache.h"
+#include "etag.h"
+
+#include "connections.h"
+
+#include "plugin.h"
+
+#include "sys-socket.h"
+
+#ifdef HAVE_ATTR_ATTRIBUTES_H
+#include <attr/attributes.h>
+#endif
+
+/*
+ * This was 'borrowed' from tcpdump.
+ *
+ *
+ * This is fun.
+ *
+ * In older BSD systems, socket addresses were fixed-length, and
+ * "sizeof (struct sockaddr)" gave the size of the structure.
+ * All addresses fit within a "struct sockaddr".
+ *
+ * In newer BSD systems, the socket address is variable-length, and
+ * there's an "sa_len" field giving the length of the structure;
+ * this allows socket addresses to be longer than 2 bytes of family
+ * and 14 bytes of data.
+ *
+ * Some commercial UNIXes use the old BSD scheme, some use the RFC 2553
+ * variant of the old BSD scheme (with "struct sockaddr_storage" rather
+ * than "struct sockaddr"), and some use the new BSD scheme.
+ *
+ * Some versions of GNU libc use neither scheme, but has an "SA_LEN()"
+ * macro that determines the size based on the address family. Other
+ * versions don't have "SA_LEN()" (as it was in drafts of RFC 2553
+ * but not in the final version). On the latter systems, we explicitly
+ * check the AF_ type to determine the length; we assume that on
+ * all those systems we have "struct sockaddr_storage".
+ */
+
+#ifdef HAVE_IPV6
+# ifndef SA_LEN
+# ifdef HAVE_SOCKADDR_SA_LEN
+# define SA_LEN(addr) ((addr)->sa_len)
+# else /* HAVE_SOCKADDR_SA_LEN */
+# ifdef HAVE_STRUCT_SOCKADDR_STORAGE
+static size_t get_sa_len(const struct sockaddr *addr) {
+ switch (addr->sa_family) {
+
+# ifdef AF_INET
+ case AF_INET:
+ return (sizeof (struct sockaddr_in));
+# endif
+
+# ifdef AF_INET6
+ case AF_INET6:
+ return (sizeof (struct sockaddr_in6));
+# endif
+
+ default:
+ return (sizeof (struct sockaddr));
+
+ }
+}
+# define SA_LEN(addr) (get_sa_len(addr))
+# else /* HAVE_SOCKADDR_STORAGE */
+# define SA_LEN(addr) (sizeof (struct sockaddr))
+# endif /* HAVE_SOCKADDR_STORAGE */
+# endif /* HAVE_SOCKADDR_SA_LEN */
+# endif /* SA_LEN */
+#endif
+
+
+
+int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen) {
+ data_string *ds;
+
+ UNUSED(srv);
+
+ if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
+ ds = data_response_init();
+ }
+ buffer_copy_string_len(ds->key, key, keylen);
+ buffer_copy_string_len(ds->value, value, vallen);
+
+ array_insert_unique(con->response.headers, (data_unset *)ds);
+
+ return 0;
+}
+
+int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen) {
+ data_string *ds;
+
+ UNUSED(srv);
+
+ /* if there already is a key by this name overwrite the value */
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, key))) {
+ buffer_copy_string(ds->value, value);
+
+ return 0;
+ }
+
+ return response_header_insert(srv, con, key, keylen, value, vallen);
+}
+
+
+int http_response_write_basic_header(server *srv, connection *con) {
+ size_t i;
+ buffer *b;
+
+ b = chunkqueue_get_prepend_buffer(con->write_queue);
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 "));
+ } else {
+ buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
+ }
+ buffer_append_long(b, con->http_status);
+ buffer_append_string_len(b, CONST_STR_LEN(" "));
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ /* add the connection header if
+ * HTTP/1.1 -> close
+ * HTTP/1.0 -> keep-alive
+ */
+ if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: ");
+ if (con->keep_alive) {
+ BUFFER_APPEND_STRING_CONST(b, "keep-alive");
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "close");
+ }
+ }
+
+ if (con->request.http_version == HTTP_VERSION_1_1 &&
+ (con->parsed_response & HTTP_DATE) == 0) {
+ /* HTTP/1.1 requires a Date: header */
+ BUFFER_APPEND_STRING_CONST(b, "\r\nDate: ");
+
+ /* cache the generated timestamp */
+ if (srv->cur_ts != srv->last_generated_date_ts) {
+ buffer_prepare_copy(srv->ts_date_str, 255);
+
+ strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts)));
+
+ srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1;
+
+ srv->last_generated_date_ts = srv->cur_ts;
+ }
+
+ buffer_append_string_buffer(b, srv->ts_date_str);
+ }
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked");
+ }
+
+ /* add all headers */
+ for (i = 0; i < con->response.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->response.headers->data[i];
+
+ if (ds->value->used && ds->key->used &&
+ 0 != strncmp(ds->key->ptr, "X-LIGHTTPD-", sizeof("X-LIGHTTPD-") - 1)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\n");
+ buffer_append_string_buffer(b, ds->key);
+ BUFFER_APPEND_STRING_CONST(b, ": ");
+ buffer_append_string_buffer(b, ds->value);
+ }
+ }
+
+ if (buffer_is_empty(con->conf.server_tag)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "PACKAGE"/"VERSION);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: ");
+ buffer_append_string_buffer(b, con->conf.server_tag);
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n");
+
+ if (con->conf.log_response_header) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsdSb",
+ "fd:", con->fd,
+ "response-header-len:", b->used - 1,
+ "\n", b);
+ }
+
+ con->bytes_header = b->used - 1;
+
+ return 0;
+}
+
+
+int http_response_write_header(server *srv, connection *con,
+ off_t file_size,
+ time_t last_mod) {
+ buffer *b;
+ size_t i;
+
+ b = chunkqueue_get_prepend_buffer(con->write_queue);
+
+ if (con->request.http_version == HTTP_VERSION_1_1) {
+ BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 ");
+ } else {
+ BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 ");
+ }
+ buffer_append_long(b, con->http_status);
+ BUFFER_APPEND_STRING_CONST(b, " ");
+ buffer_append_string(b, get_http_status_name(con->http_status));
+
+ if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: ");
+ buffer_append_string(b, con->keep_alive ? "keep-alive" : "close");
+ }
+
+ if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked");
+ }
+
+ /* HTTP/1.1 requires a Date: header */
+ BUFFER_APPEND_STRING_CONST(b, "\r\nDate: ");
+
+ /* cache the generated timestamp */
+ if (srv->cur_ts != srv->last_generated_date_ts) {
+ buffer_prepare_copy(srv->ts_date_str, 255);
+
+ strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts)));
+
+ srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1;
+
+ srv->last_generated_date_ts = srv->cur_ts;
+ }
+
+ buffer_append_string_buffer(b, srv->ts_date_str);
+
+ /* no Last-Modified specified */
+ if (last_mod && NULL == array_get_element(con->response.headers, "Last-Modified")) {
+ struct tm *tm;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ if (srv->mtime_cache[i].mtime == last_mod) break;
+
+ if (srv->mtime_cache[i].mtime == 0) {
+ srv->mtime_cache[i].mtime = last_mod;
+
+ buffer_prepare_copy(srv->mtime_cache[i].str, 1024);
+
+ tm = gmtime(&(srv->mtime_cache[i].mtime));
+ srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr,
+ srv->mtime_cache[i].str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ srv->mtime_cache[i].str->used++;
+ break;
+ }
+ }
+
+ if (i == FILE_CACHE_MAX) {
+ i = 0;
+
+ srv->mtime_cache[i].mtime = last_mod;
+ buffer_prepare_copy(srv->mtime_cache[i].str, 1024);
+ tm = gmtime(&(srv->mtime_cache[i].mtime));
+ srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr,
+ srv->mtime_cache[i].str->size - 1,
+ "%a, %d %b %Y %H:%M:%S GMT", tm);
+ srv->mtime_cache[i].str->used++;
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\nLast-Modified: ");
+ buffer_append_string_buffer(b, srv->mtime_cache[i].str);
+ }
+
+ if (file_size >= 0 && con->http_status != 304) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nContent-Length: ");
+ buffer_append_off_t(b, file_size);
+ }
+
+ if (con->physical.path->used && con->physical.etag->used) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nETag: ");
+ buffer_append_string_buffer(b, con->physical.etag);
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\nAccept-Ranges: bytes");
+
+ /* add all headers */
+ for (i = 0; i < con->response.headers->used; i++) {
+ data_string *ds;
+
+ ds = (data_string *)con->response.headers->data[i];
+
+ if (ds->value->used && ds->key->used &&
+ 0 != strncmp(ds->key->ptr, "X-LIGHTTPD-", sizeof("X-LIGHTTPD-") - 1)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\n");
+ buffer_append_string_buffer(b, ds->key);
+ BUFFER_APPEND_STRING_CONST(b, ": ");
+ buffer_append_string_buffer(b, ds->value);
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "bb",
+ ds->key, ds->value);
+#endif
+ }
+ }
+
+ if (buffer_is_empty(con->conf.server_tag)) {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "PACKAGE"/"VERSION);
+ } else {
+ BUFFER_APPEND_STRING_CONST(b, "\r\nServer: ");
+ buffer_append_string_buffer(b, con->conf.server_tag);
+ }
+
+ BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n");
+
+ con->bytes_header = b->used - 1;
+
+ if (con->conf.log_response_header) {
+ log_error_write(srv, __FILE__, __LINE__, "sSb", "Response-Header:", "\n", b);
+ }
+
+ return 0;
+}
+
+static int http_response_parse_range(server *srv, connection *con) {
+ struct stat st;
+ int multipart = 0;
+ int error;
+ off_t start, end;
+ const char *s, *minus;
+ char *boundary = "fkj49sn38dcn3";
+ const char *content_type = NULL;
+ data_string *ds;
+
+ if (-1 == stat(con->physical.path->ptr, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "stat failed: ", strerror(errno));
+ return -1;
+ }
+
+ start = 0;
+ end = st.st_size - 1;
+
+ con->response.content_length = 0;
+
+ if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) {
+ content_type = ds->value->ptr;
+ }
+
+ for (s = con->request.http_range, error = 0;
+ !error && *s && NULL != (minus = strchr(s, '-')); ) {
+ char *err;
+ long la, le;
+
+ if (s == minus) {
+ /* -<stop> */
+
+ le = strtol(s, &err, 10);
+
+ if (le == 0) {
+ /* RFC 2616 - 14.35.1 */
+
+ con->http_status = 416;
+ error = 1;
+ } else if (*err == '\0') {
+ /* end */
+ s = err;
+
+ end = st.st_size - 1;
+ start = st.st_size + le;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = st.st_size - 1;
+ start = st.st_size + le;
+ } else {
+ error = 1;
+ }
+
+ } else if (*(minus+1) == '\0' || *(minus+1) == ',') {
+ /* <start>- */
+
+ la = strtol(s, &err, 10);
+
+ if (err == minus) {
+ /* ok */
+
+ if (*(err + 1) == '\0') {
+ s = err + 1;
+
+ end = st.st_size - 1;
+ start = la;
+
+ } else if (*(err + 1) == ',') {
+ multipart = 1;
+ s = err + 2;
+
+ end = st.st_size - 1;
+ start = la;
+ } else {
+ error = 1;
+ }
+ } else {
+ /* error */
+ error = 1;
+ }
+ } else {
+ /* <start>-<stop> */
+
+ la = strtol(s, &err, 10);
+
+ if (err == minus) {
+ le = strtol(minus+1, &err, 10);
+
+ /* RFC 2616 - 14.35.1 */
+ if (la > le) {
+ error = 1;
+ }
+
+ if (*err == '\0') {
+ /* ok, end*/
+ s = err;
+
+ end = le;
+ start = la;
+ } else if (*err == ',') {
+ multipart = 1;
+ s = err + 1;
+
+ end = le;
+ start = la;
+ } else {
+ /* error */
+
+ error = 1;
+ }
+ } else {
+ /* error */
+
+ error = 1;
+ }
+ }
+
+ if (!error) {
+ if (start < 0) start = 0;
+
+ /* RFC 2616 - 14.35.1 */
+ if (end > st.st_size - 1) end = st.st_size - 1;
+
+ if (start > st.st_size - 1) {
+ error = 1;
+
+ con->http_status = 416;
+ }
+ }
+
+ if (!error) {
+ if (multipart) {
+ /* write boundary-header */
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string(b, "\r\n--");
+ buffer_append_string(b, boundary);
+
+ /* write Content-Range */
+ buffer_append_string(b, "\r\nContent-Range: bytes ");
+ buffer_append_off_t(b, start);
+ buffer_append_string(b, "-");
+ buffer_append_off_t(b, end);
+ buffer_append_string(b, "/");
+ buffer_append_off_t(b, st.st_size);
+
+ buffer_append_string(b, "\r\nContent-Type: ");
+ buffer_append_string(b, content_type);
+
+ /* write END-OF-HEADER */
+ buffer_append_string(b, "\r\n\r\n");
+
+ con->response.content_length += b->used - 1;
+
+ }
+
+ chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1);
+ con->response.content_length += end - start + 1;
+ }
+ }
+
+ /* something went wrong */
+ if (error) {
+ return 0;
+ }
+
+ if (multipart) {
+ /* add boundary end */
+ buffer *b;
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+
+ buffer_copy_string_len(b, "\r\n--", 4);
+ buffer_append_string(b, boundary);
+ buffer_append_string_len(b, "--\r\n", 4);
+
+ con->response.content_length += b->used - 1;
+
+ /* set header-fields */
+
+ buffer_copy_string(srv->range_buf, "multipart/byteranges; boundary=");
+ buffer_append_string(srv->range_buf, boundary);
+
+ /* overwrite content-type */
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(srv->range_buf));
+ } else {
+ /* add Content-Range-header */
+
+ buffer_copy_string(srv->range_buf, "bytes ");
+ buffer_append_off_t(srv->range_buf, start);
+ buffer_append_string(srv->range_buf, "-");
+ buffer_append_off_t(srv->range_buf, end);
+ buffer_append_string(srv->range_buf, "/");
+ buffer_append_off_t(srv->range_buf, st.st_size);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(srv->range_buf));
+ }
+
+ /* ok, the file is set-up */
+ con->http_status = 206;
+
+ return 0;
+}
+
+typedef struct {
+ char **ptr;
+ size_t used;
+ size_t size;
+} string_buffer;
+
+static int http_list_directory(server *srv, connection *con, buffer *dir) {
+ DIR *d;
+ struct dirent *dent;
+ buffer *b, *date_buf;
+ string_buffer *sb;
+ size_t i;
+
+ if (NULL == (d = opendir(dir->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opendir failed:", dir, strerror(errno));
+ return -1;
+ }
+
+ b = chunkqueue_get_append_buffer(con->write_queue);
+ buffer_copy_string(b,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+ " <head>\n"
+ " <title>Directory Listing for ");
+ buffer_append_string_html_encoded(b, con->uri.path->ptr);
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "</title>\n"
+ " <style type=\"text/css\">\n");
+
+ BUFFER_APPEND_STRING_CONST(b,
+ "th.dirlisting { background-color: black; color: white; }\n"
+ "table.dirlisting { border: black solid thin; }\n"
+ "td.dirlisting { background-color: #f0f0f0; }\n"
+ "td.dirlistingnumber { background-color: #f0f0f0; text-align: right }\n"
+ );
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </style>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h2>Directory Listing for ");
+ buffer_append_string_html_encoded(b, con->uri.path->ptr);
+ BUFFER_APPEND_STRING_CONST(b,"</h2>\n <table class='dirlisting'>\n");
+ BUFFER_APPEND_STRING_CONST(b," <tr class='dirlisting'><th class='dirlisting'>date</th><th class='dirlisting'>size</th><th class='dirlisting'>type</th><th class='dirlisting'>name</th></tr>\n");
+
+
+ /* allocate memory */
+ sb = calloc(1, sizeof(*sb));
+ assert(sb);
+
+ sb->ptr = NULL;
+ sb->used = 0;
+ sb->size = 0;
+
+ while(NULL != (dent = readdir(d))) {
+ size_t j;
+ if (sb->size == 0) {
+ sb->size = 4;
+ sb->ptr = malloc(sizeof(*sb->ptr) * sb->size);
+ assert(sb->ptr);
+ } else if (sb->used == sb->size) {
+ sb->size += 4;
+ sb->ptr = realloc(sb->ptr, sizeof(*sb->ptr) * sb->size);
+ assert(sb->ptr);
+ }
+
+ for (i = 0; i < sb->used; i++) {
+ if (strcmp(dent->d_name, sb->ptr[i]) < 0) {
+ break;
+ }
+ }
+
+ /* <left><right> */
+ /* <left>[i]<right> */
+
+ for (j = sb->used - 1; sb->used && j >= i && (j+1) > 0; j--) {
+ sb->ptr[j + 1] = sb->ptr[j];
+ }
+ sb->ptr[i] = strdup(dent->d_name);
+
+ sb->used++;
+
+ }
+
+ closedir(d);
+
+ date_buf = buffer_init();
+ buffer_prepare_copy(date_buf, 22);
+ for (i = 0; i < sb->used; i++) {
+ struct stat st;
+ struct tm tm;
+ size_t s_len = strlen(sb->ptr[i]);
+
+
+ buffer_copy_string(srv->tmp_buf, dir->ptr);
+ buffer_append_string(srv->tmp_buf, sb->ptr[i]);
+
+ if (0 != stat(srv->tmp_buf->ptr, &st)) {
+ free(sb->ptr[i]);
+ continue;
+ }
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " <tr><td class='dirlisting'>");
+
+#ifdef HAVE_LOCALTIME_R
+ /* localtime_r is faster */
+ localtime_r(&(st.st_mtime), &tm);
+
+ date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", &tm);
+#else
+ date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(st.st_mtime)));
+#endif
+ date_buf->used++;
+
+ buffer_append_string_buffer(b, date_buf);
+ BUFFER_APPEND_STRING_CONST(b,
+ "</td><td class='dirlistingnumber'>");
+
+ buffer_append_off_t(b, st.st_size);
+
+ /* mime type */
+ BUFFER_APPEND_STRING_CONST(b,
+ "</td><td class='dirlisting'>");
+
+ if (S_ISDIR(st.st_mode)) {
+ buffer_append_string_rfill(b, "directory", 28);
+ } else {
+ size_t k;
+ unsigned short have_content_type = 0;
+
+#ifdef HAVE_XATTR
+ char attrval[128];
+ int attrlen = sizeof(attrval) - 1;
+
+ if(con->conf.use_xattr && 0 == attr_get(srv->tmp_buf->ptr, "Content-Type", attrval, &attrlen, 0)) {
+ attrval[attrlen] = 0;
+ buffer_append_string_rfill(b, attrval, 28);
+ have_content_type = 1;
+ }
+#endif
+
+ if(!have_content_type) {
+ for (k = 0; k < con->conf.mimetypes->used; k++) {
+ data_string *ds = (data_string *)con->conf.mimetypes->data[k];
+ size_t ct_len;
+
+ if (ds->key->used == 0) continue;
+
+ ct_len = ds->key->used - 1;
+
+ if (s_len < ct_len) continue;
+
+ if (0 == strncmp(sb->ptr[i] + s_len - ct_len, ds->key->ptr, ct_len)) {
+ buffer_append_string_rfill(b, ds->value->ptr, 28);
+ break;
+ }
+ }
+
+ if (k == con->conf.mimetypes->used) {
+ buffer_append_string_rfill(b, "application/octet-stream", 28);
+ }
+ }
+ }
+
+ /* URL */
+ BUFFER_APPEND_STRING_CONST(b,"</td><td class='dirlisting'><a href=\"");
+ /* URL encode */
+ buffer_append_string_url_encoded(b, sb->ptr[i]);
+ if (S_ISDIR(st.st_mode)) {
+ BUFFER_APPEND_STRING_CONST(b,"/");
+ }
+ BUFFER_APPEND_STRING_CONST(b,"\">");
+ /* HTML encode */
+ buffer_append_string_html_encoded(b, sb->ptr[i]);
+ if (S_ISDIR(st.st_mode)) {
+ BUFFER_APPEND_STRING_CONST(b,"/");
+ }
+ BUFFER_APPEND_STRING_CONST(b,"</a></td></tr>\n");
+
+
+ free(sb->ptr[i]);
+ }
+
+ buffer_free(date_buf);
+ free(sb->ptr);
+ free(sb);
+
+ response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
+
+ BUFFER_APPEND_STRING_CONST(b,
+ " </table>\n"
+ " </body>\n"
+ "</html>\n" );
+
+ con->file_finished = 1;
+
+ return 0;
+}
+
+int http_response_redirect_to_directory(server *srv, connection *con) {
+ buffer *o;
+
+ o = buffer_init();
+
+ if (con->conf.is_ssl) {
+ buffer_copy_string(o, "https://");
+ } else {
+ buffer_copy_string(o, "http://");
+ }
+ if (con->uri.authority->used) {
+ buffer_append_string_buffer(o, con->uri.authority);
+ } else {
+ /* get the name of the currently connected socket */
+ struct hostent *he;
+#ifdef HAVE_IPV6
+ char hbuf[256];
+#endif
+ sock_addr our_addr;
+ socklen_t our_addr_len;
+
+ our_addr_len = sizeof(our_addr);
+
+ if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) {
+ con->http_status = 500;
+
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "can't get sockname", strerror(errno));
+
+ buffer_free(o);
+ return 0;
+ }
+
+
+ /* Lookup name: secondly try to get hostname for bind address */
+ switch(our_addr.plain.sa_family) {
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ if (0 != getnameinfo((const struct sockaddr *)(&our_addr.ipv6),
+ SA_LEN((const struct sockaddr *)&our_addr.ipv6),
+ hbuf, sizeof(hbuf), NULL, 0, 0)) {
+
+ char dst[INET6_ADDRSTRLEN];
+
+ log_error_write(srv, __FILE__, __LINE__,
+ "SSSS", "NOTICE: getnameinfo failed: ",
+ strerror(errno), ", using ip-address instead");
+
+ buffer_append_string(o,
+ inet_ntop(AF_INET6, (char *)&our_addr.ipv6.sin6_addr,
+ dst, sizeof(dst)));
+ } else {
+ buffer_append_string(o, hbuf);
+ }
+ break;
+#endif
+ case AF_INET:
+ if (NULL == (he = gethostbyaddr((char *)&our_addr.ipv4.sin_addr, sizeof(struct in_addr), AF_INET))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "SSSS", "NOTICE: gethostbyaddr failed: ",
+ hstrerror(h_errno), ", using ip-address instead");
+
+ buffer_append_string(o, inet_ntoa(our_addr.ipv4.sin_addr));
+ } else {
+ buffer_append_string(o, he->h_name);
+ }
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__,
+ "S", "ERROR: unsupported address-type");
+
+ buffer_free(o);
+ return -1;
+ }
+
+ if (!((con->conf.is_ssl == 0 && srv->srvconf.port == 80) ||
+ (con->conf.is_ssl == 1 && srv->srvconf.port == 443))) {
+ buffer_append_string(o, ":");
+ buffer_append_long(o, srv->srvconf.port);
+ }
+ }
+ buffer_append_string_buffer(o, con->uri.path);
+ buffer_append_string(o, "/");
+ if (!buffer_is_empty(con->uri.query)) {
+ buffer_append_string(o, "?");
+ buffer_append_string_buffer(o, con->uri.query);
+ }
+
+ response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(o));
+
+ con->http_status = 301;
+
+ buffer_free(o);
+
+ return 0;
+}
+
+
+handler_t http_response_prepare(server *srv, connection *con) {
+ handler_t r;
+
+ /* looks like someone has already done a decision */
+ if (con->mode == DIRECT &&
+ (con->http_status != 0 && con->http_status != 200)) {
+ /* remove a packets in the queue */
+ if (con->file_finished == 0) {
+ chunkqueue_reset(con->write_queue);
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ /* no decision yet, build conf->filename */
+ if (con->mode == DIRECT && con->physical.path->used == 0) {
+ char *qstr;
+
+ config_patch_connection(srv, con, CONST_STR_LEN("SERVERsocket")); /* SERVERsocket */
+
+ /**
+ * prepare strings
+ *
+ * - uri.path_raw
+ * - uri.path (secure)
+ * - uri.query
+ *
+ */
+
+ /**
+ * Name according to RFC 2396
+ *
+ * - scheme
+ * - authority
+ * - path
+ * - query
+ *
+ * (scheme)://(authority)(path)?(query)
+ *
+ *
+ */
+
+ buffer_copy_string(con->uri.scheme, con->conf.is_ssl ? "http" : "https");
+ buffer_copy_string_buffer(con->uri.authority, con->request.http_host);
+
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPhost")); /* Host: */
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPreferer")); /* Referer: */
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPuseragent")); /* User-Agent: */
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPcookie")); /* Cookie: */
+
+ /** extract query string from request.uri */
+ if (NULL != (qstr = strchr(con->request.uri->ptr, '?'))) {
+ buffer_copy_string (con->uri.query, qstr + 1);
+ buffer_copy_string_len(con->uri.path_raw, con->request.uri->ptr, qstr - con->request.uri->ptr);
+ } else {
+ buffer_reset (con->uri.query);
+ buffer_copy_string_buffer(con->uri.path_raw, con->request.uri);
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- splitting Request-URI");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Request-URI : ", con->request.uri);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-scheme : ", con->uri.scheme);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-authority: ", con->uri.authority);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path_raw);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-query : ", con->uri.query);
+ }
+
+ /* disable keep-alive if requested */
+
+ if (con->request_count > con->conf.max_keep_alive_requests) {
+ con->keep_alive = 0;
+ }
+
+
+ /**
+ *
+ * call plugins
+ *
+ * - based on the raw URL
+ *
+ */
+
+ switch(r = plugins_call_handle_uri_raw(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "handle_uri_raw: unknown return value", r);
+ break;
+ }
+
+ /* build filename
+ *
+ * - decode url-encodings (e.g. %20 -> ' ')
+ * - remove path-modifiers (e.g. /../)
+ */
+
+
+
+ buffer_copy_string_buffer(srv->tmp_buf, con->uri.path_raw);
+ buffer_urldecode(srv->tmp_buf);
+ buffer_path_simplify(&(srv->dot_stack), con->uri.path, srv->tmp_buf);
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- sanatising URI");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path);
+ }
+
+ /**
+ *
+ * call plugins
+ *
+ * - based on the clean URL
+ *
+ */
+
+ config_patch_connection(srv, con, CONST_STR_LEN("HTTPurl")); /* HTTPurl */
+
+ switch(r = plugins_call_handle_uri_clean(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+
+ /***
+ *
+ * border
+ *
+ * logical filename (URI) becomes a physical filename here
+ *
+ *
+ *
+ */
+
+
+
+
+ /* 1. stat()
+ * ... ISREG() -> ok, go on
+ * ... ISDIR() -> index-file -> redirect
+ *
+ * 2. pathinfo()
+ * ... ISREG()
+ *
+ * 3. -> 404
+ *
+ */
+
+ /*
+ * SEARCH DOCUMENT ROOT
+ */
+
+ /* set a default */
+
+ buffer_copy_string_buffer(con->physical.doc_root, con->conf.document_root);
+ buffer_copy_string_buffer(con->physical.rel_path, con->uri.path);
+
+ buffer_reset(con->physical.path);
+
+ /* the docroot plugin should set the doc_root and might also set the physical.path
+ * for us (all vhost-plugins are supposed to set the doc_root, the alias plugin
+ * sets the path too)
+ * */
+ switch(r = plugins_call_handle_docroot(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_FINISHED:
+ case HANDLER_COMEBACK:
+ case HANDLER_WAIT_FOR_EVENT:
+ case HANDLER_ERROR:
+ return r;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "");
+ break;
+ }
+
+ if (buffer_is_empty(con->physical.path)) {
+ /**
+ * create physical filename
+ * -> physical.path = docroot + rel_path
+ *
+ */
+
+ buffer_copy_string_buffer(con->physical.path, con->physical.doc_root);
+ BUFFER_APPEND_SLASH(con->physical.path);
+ if (con->physical.rel_path->ptr[0] == '/') {
+ buffer_append_string_len(con->physical.path, con->physical.rel_path->ptr + 1, con->physical.rel_path->used - 2);
+ } else {
+ buffer_append_string_buffer(con->physical.path, con->physical.rel_path);
+ }
+ }
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- logical -> physical");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+ }
+
+ /*
+ * only if we are still in DIRECT mode we check for the real existence of the file
+ *
+ */
+
+ if (con->mode == DIRECT) {
+ char *slash = NULL;
+ char *pathinfo = NULL;
+ int found = 0;
+ size_t k;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- handling physical path");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ switch (file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) {
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ case HANDLER_ERROR:
+ if (errno == EACCES) {
+ con->http_status = 403;
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ if (errno != ENOENT &&
+ errno != ENOTDIR) {
+ /* we have no idea what happend. let's tell the user so. */
+
+ con->http_status = 500;
+ buffer_reset(con->physical.path);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsb",
+ "file not found ... or so: ", strerror(errno),
+ con->uri.path,
+ "->", con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ /* not found, perhaps PATHINFO */
+ buffer_copy_string_buffer(srv->tmp_buf, con->physical.rel_path);
+
+ /*
+ *
+ * FIXME:
+ *
+ * Check for PATHINFO fall to dir of
+ *
+ * /a is a dir and
+ *
+ * /a/b/c is requested
+ *
+ */
+
+ do {
+ struct stat st;
+
+ buffer_copy_string_buffer(con->physical.path, con->physical.doc_root);
+ BUFFER_APPEND_SLASH(con->physical.path);
+ if (slash) {
+ buffer_append_string_len(con->physical.path, srv->tmp_buf->ptr, slash - srv->tmp_buf->ptr);
+ } else {
+ buffer_append_string_buffer(con->physical.path, srv->tmp_buf);
+ }
+
+ if (0 == stat(con->physical.path->ptr, &(st)) &&
+ S_ISREG(st.st_mode)) {
+ found = 1;
+ break;
+ }
+
+ if (pathinfo != NULL) {
+ *pathinfo = '\0';
+ }
+ slash = strrchr(srv->tmp_buf->ptr, '/');
+
+ if (pathinfo != NULL) {
+ /* restore '/' */
+ *pathinfo = '/';
+ }
+
+ if (slash) pathinfo = slash;
+ } while ((found == 0) && (slash != NULL) && (slash != srv->tmp_buf->ptr));
+
+ if (found == 0) {
+ /* no it really doesn't exists */
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "file not found:", con->uri.path,
+ "->", con->physical.path);
+ }
+
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+
+ /* we have a PATHINFO */
+ if (pathinfo) {
+ buffer_copy_string(con->request.pathinfo, pathinfo);
+
+ /*
+ * shorten uri.path
+ */
+
+ con->uri.path->used -= strlen(pathinfo);
+ con->uri.path->ptr[con->uri.path->used - 1] = '\0';
+
+
+ }
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- after pathinfo check");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Pathinfo :", con->request.pathinfo);
+ }
+
+ /* setup the right file cache entry (FCE) */
+ switch (file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) {
+ case HANDLER_ERROR:
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "file not found:", con->uri.path,
+ "->", con->physical.path);
+ }
+
+ return HANDLER_FINISHED;
+ case HANDLER_WAIT_FOR_FD:
+ return HANDLER_WAIT_FOR_FD;
+ case HANDLER_GO_ON:
+ break;
+ default:
+ break;
+ }
+
+ break;
+ case HANDLER_GO_ON:
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "-- file found");
+ log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path);
+ }
+
+ if (S_ISDIR(con->fce->st.st_mode)) {
+ if (con->physical.path->ptr[con->physical.path->used - 2] != '/') {
+ /* redirect to .../ */
+
+ http_response_redirect_to_directory(srv, con);
+
+ return HANDLER_FINISHED;
+ } else {
+ found = 0;
+ /* indexfile */
+
+ for (k = 0; !found && (k < con->conf.indexfiles->used); k++) {
+ data_string *ds = (data_string *)con->conf.indexfiles->data[k];
+
+ buffer_copy_string_buffer(srv->tmp_buf, con->physical.path);
+ buffer_append_string_buffer(srv->tmp_buf, ds->value);
+
+ switch (file_cache_get_entry(srv, con, srv->tmp_buf, &(con->fce))) {
+ case HANDLER_GO_ON:
+ /* rewrite uri.path to the real path (/ -> /index.php) */
+ buffer_append_string_buffer(con->uri.path, ds->value);
+
+ found = 1;
+ break;
+ case HANDLER_ERROR:
+
+ if (errno == EACCES) {
+ con->http_status = 403;
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ if (errno != ENOENT &&
+ errno != ENOTDIR) {
+ /* we have no idea what happend. let's tell the user so. */
+
+ con->http_status = 500;
+ buffer_reset(con->physical.path);
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsb",
+ "file not found ... or so: ", strerror(errno),
+ con->uri.path,
+ "->", con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!found &&
+ (k == con->conf.indexfiles->used)) {
+ /* directory listing ? */
+
+ buffer_reset(srv->tmp_buf);
+
+ if (con->conf.dir_listing == 0) {
+ /* dirlisting disabled */
+ con->http_status = 403;
+ } else if (0 != http_list_directory(srv, con, con->physical.path)) {
+ /* dirlisting failed */
+ con->http_status = 403;
+ }
+
+ buffer_reset(con->physical.path);
+
+ return HANDLER_FINISHED;
+ }
+
+ buffer_copy_string_buffer(con->physical.path, srv->tmp_buf);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!S_ISREG(con->fce->st.st_mode)) {
+ con->http_status = 404;
+
+ if (con->conf.log_file_not_found) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsb",
+ "not a regular file:", con->uri.path,
+ "->", con->fce->name);
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ /* call the handlers */
+ switch(r = plugins_call_handle_subrequest_start(srv, con)) {
+ case HANDLER_FINISHED:
+ /* request was handled */
+ break;
+ case HANDLER_GO_ON:
+ /* request was not handled */
+ break;
+ default:
+ /* something strange happend */
+ return r;
+ }
+
+ /* ok, noone has handled the file up to now, so we do the fileserver-stuff */
+ if (r == HANDLER_GO_ON) {
+ /* DIRECT */
+
+ /* set response content-type */
+
+ if (buffer_is_empty(con->fce->content_type)) {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
+ } else {
+ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(con->fce->content_type));
+ }
+
+ /* generate e-tag */
+ etag_mutate(con->physical.etag, con->fce->etag);
+
+ /*
+ * 14.26 If-None-Match
+ * [...]
+ * If none of the entity tags match, then the server MAY perform the
+ * requested method as if the If-None-Match header field did not exist,
+ * but MUST also ignore any If-Modified-Since header field(s) in the
+ * request. That is, if no entity tags match, then the server MUST NOT
+ * return a 304 (Not Modified) response.
+ */
+
+ /* last-modified handling */
+ if (con->http_status == 0 &&
+ con->request.http_if_none_match) {
+ if (etag_is_equal(con->physical.etag, con->request.http_if_none_match)) {
+ if (con->request.http_method == HTTP_METHOD_GET ||
+ con->request.http_method == HTTP_METHOD_HEAD) {
+
+ /* check if etag + last-modified */
+ if (con->request.http_if_modified_since) {
+ char buf[64];
+ struct tm *tm;
+ size_t used_len;
+ char *semicolon;
+
+ tm = gmtime(&(con->fce->st.st_mtime));
+ strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) {
+ used_len = strlen(con->request.http_if_modified_since);
+ } else {
+ used_len = semicolon - con->request.http_if_modified_since;
+ }
+
+ if (0 == strncmp(con->request.http_if_modified_since, buf, used_len)) {
+ con->http_status = 304;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ con->request.http_if_modified_since, buf);
+
+ con->http_status = 412;
+ }
+ } else {
+ con->http_status = 304;
+ }
+ } else {
+ con->http_status = 412;
+ }
+ }
+ } else if (con->http_status == 0 && con->request.http_if_modified_since) {
+ char buf[64];
+ struct tm *tm;
+ size_t used_len;
+ char *semicolon;
+
+ tm = gmtime(&(con->fce->st.st_mtime));
+ strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) {
+ used_len = strlen(con->request.http_if_modified_since);
+ } else {
+ used_len = semicolon - con->request.http_if_modified_since;
+ }
+
+ if (0 == strncmp(con->request.http_if_modified_since, buf, used_len)) {
+ con->http_status = 304;
+ }
+ }
+
+ if (con->http_status == 0 && con->request.http_range) {
+ http_response_parse_range(srv, con);
+ } else if (con->http_status == 0) {
+ switch(r = plugins_call_handle_physical_path(srv, con)) {
+ case HANDLER_GO_ON:
+ break;
+ default:
+ return r;
+ }
+ }
+ }
+ }
+
+ switch(r = plugins_call_handle_subrequest(srv, con)) {
+ case HANDLER_GO_ON:
+ /* request was not handled, looks like we are done */
+
+ return HANDLER_FINISHED;
+ case HANDLER_FINISHED:
+ /* request is finished */
+ default:
+ /* something strange happend */
+ return r;
+ }
+
+ /* can't happen */
+ return HANDLER_COMEBACK;
+}
diff --git a/src/response.h b/src/response.h
new file mode 100644
index 00000000..e7d7dc74
--- /dev/null
+++ b/src/response.h
@@ -0,0 +1,18 @@
+#ifndef _RESPONSE_H_
+#define _RESPONSE_H_
+
+#include "server.h"
+
+int http_response_parse(server *srv, connection *con);
+int http_response_write_basic_header(server *srv, connection *con);
+int http_response_write_header(server *srv, connection *con,
+ off_t file_size,
+ time_t last_mod);
+
+int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen);
+int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen);
+
+handler_t http_response_prepare(server *srv, connection *con);
+int http_response_redirect_to_directory(server *srv, connection *con);
+
+#endif
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 00000000..7573caef
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,1021 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <signal.h>
+#include <assert.h>
+#include <locale.h>
+
+#include <stdio.h>
+
+#include "server.h"
+#include "buffer.h"
+#include "network.h"
+#include "log.h"
+#include "keyvalue.h"
+#include "response.h"
+#include "request.h"
+#include "chunk.h"
+#include "http_chunk.h"
+#include "fdevent.h"
+#include "connections.h"
+#include "file_cache.h"
+#include "plugin.h"
+#include "joblist.h"
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_PWD_H
+#include <grp.h>
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+const char *patches[] = { "SERVERsocket", "HTTPurl", "HTTPhost", "HTTPreferer", "HTTPuseragent", "HTTPcookie", NULL };
+
+
+#ifndef __sgi
+/* IRIX doesn't like the alarm based time() optimization */
+/* #define USE_ALARM */
+#endif
+
+static sig_atomic_t srv_shutdown = 0;
+static sig_atomic_t handle_sig_alarm = 1;
+static sig_atomic_t handle_sig_hup = 0;
+
+#if defined(HAVE_SIGACTION) && defined(SA_SIGINFO)
+static void sigaction_handler(int sig, siginfo_t *si, void *context) {
+ UNUSED(si);
+ UNUSED(context);
+
+ switch (sig) {
+ case SIGTERM: srv_shutdown = 1; break;
+ case SIGALRM: handle_sig_alarm = 1; break;
+ case SIGHUP: handle_sig_hup = 1; break;
+ }
+}
+#elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION)
+static void signal_handler(int sig) {
+ switch (sig) {
+ case SIGTERM: srv_shutdown = 1; break;
+ case SIGALRM: handle_sig_alarm = 1; break;
+ case SIGHUP: handle_sig_hup = 1; break;
+ }
+}
+#endif
+
+#ifdef HAVE_FORK
+static void daemonize(void) {
+#ifdef SIGTTOU
+ signal(SIGTTOU, SIG_IGN);
+#endif
+#ifdef SIGTTIN
+ signal(SIGTTIN, SIG_IGN);
+#endif
+#ifdef SIGTSTP
+ signal(SIGTSTP, SIG_IGN);
+#endif
+ if (fork() != 0) exit(0);
+
+ if (setsid() == -1) exit(0);
+
+ signal(SIGHUP, SIG_IGN);
+
+ if (fork() != 0) exit(0);
+
+ chdir("/");
+
+ umask(0);
+}
+#endif
+
+static server *server_init(void) {
+ int i;
+
+ server *srv = calloc(1, sizeof(*srv));
+ assert(srv);
+#define CLEAN(x) \
+ srv->x = buffer_init();
+
+ CLEAN(response_header);
+ CLEAN(parse_full_path);
+ CLEAN(ts_debug_str);
+ CLEAN(ts_date_str);
+ CLEAN(error_log);
+ CLEAN(response_range);
+ CLEAN(tmp_buf);
+ CLEAN(file_cache_etag);
+ CLEAN(range_buf);
+ CLEAN(empty_string);
+
+ buffer_copy_string(srv->empty_string, "");
+
+ CLEAN(srvconf.error_logfile);
+ CLEAN(srvconf.groupname);
+ CLEAN(srvconf.username);
+ CLEAN(srvconf.changeroot);
+ CLEAN(srvconf.bindhost);
+ CLEAN(srvconf.errorfile_prefix);
+ CLEAN(srvconf.license);
+ CLEAN(srvconf.event_handler);
+ CLEAN(srvconf.pid_file);
+
+ CLEAN(tmp_chunk_len);
+#undef CLEAN
+
+#define CLEAN(x) \
+ srv->x = array_init();
+
+ CLEAN(config_context);
+ CLEAN(config_touched);
+#undef CLEAN
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ srv->mtime_cache[i].str = buffer_init();
+ }
+
+ srv->cur_ts = time(NULL);
+ srv->startup_ts = srv->cur_ts;
+
+ srv->conns = calloc(1, sizeof(*srv->conns));
+ assert(srv->conns);
+
+ srv->joblist = calloc(1, sizeof(*srv->joblist));
+ assert(srv->joblist);
+
+ srv->fdwaitqueue = calloc(1, sizeof(*srv->fdwaitqueue));
+ assert(srv->fdwaitqueue);
+
+ srv->file_cache = file_cache_init();
+ assert(srv->file_cache);
+
+ srv->srvconf.modules = array_init();
+
+ /* use syslog */
+ srv->log_error_fd = -1;
+
+ srv->split_vals = array_init();
+
+
+ srv->dot_stack.ptr = NULL;
+ srv->dot_stack.used = 0;
+ srv->dot_stack.size = 0;
+
+ srv->config_patches = buffer_array_init();
+ for (i = 0; patches[i]; i++) {
+ buffer *b;
+
+ b = buffer_array_append_get_buffer(srv->config_patches);
+ buffer_copy_string(b, patches[i]);
+ }
+
+ return srv;
+}
+
+static void server_free(server *srv) {
+ size_t i;
+
+ for (i = 0; i < FILE_CACHE_MAX; i++) {
+ buffer_free(srv->mtime_cache[i].str);
+ }
+
+ buffer_array_free(srv->config_patches);
+
+#define CLEAN(x) \
+ buffer_free(srv->x);
+
+ CLEAN(response_header);
+ CLEAN(parse_full_path);
+ CLEAN(ts_debug_str);
+ CLEAN(ts_date_str);
+ CLEAN(error_log);
+ CLEAN(response_range);
+ CLEAN(tmp_buf);
+ CLEAN(file_cache_etag);
+ CLEAN(range_buf);
+ CLEAN(empty_string);
+
+ CLEAN(srvconf.error_logfile);
+ CLEAN(srvconf.groupname);
+ CLEAN(srvconf.username);
+ CLEAN(srvconf.changeroot);
+ CLEAN(srvconf.bindhost);
+ CLEAN(srvconf.errorfile_prefix);
+ CLEAN(srvconf.license);
+ CLEAN(srvconf.event_handler);
+ CLEAN(srvconf.pid_file);
+
+ CLEAN(tmp_chunk_len);
+#undef CLEAN
+
+#if 0
+ fdevent_unregister(srv->ev, srv->fd);
+#endif
+ fdevent_free(srv->ev);
+
+ connections_free(srv);
+ free(srv->conns);
+
+ if (srv->config_storage) {
+ for (i = 0; i < srv->config_context->used; i++) {
+ specific_config *s = srv->config_storage[i];
+ buffer_free(s->document_root);
+ buffer_free(s->server_name);
+ buffer_free(s->server_tag);
+ buffer_free(s->ssl_pemfile);
+ buffer_free(s->error_handler);
+ array_free(s->indexfiles);
+ array_free(s->mimetypes);
+
+ free(s);
+ }
+ free(srv->config_storage);
+ srv->config_storage = NULL;
+ }
+
+#define CLEAN(x) \
+ array_free(srv->x);
+
+ CLEAN(config_context);
+ CLEAN(config_touched);
+#undef CLEAN
+
+ joblist_free(srv, srv->joblist);
+ fdwaitqueue_free(srv, srv->fdwaitqueue);
+
+ file_cache_free(srv, srv->file_cache);
+
+ array_free(srv->srvconf.modules);
+ array_free(srv->split_vals);
+
+ for (i = 0; i < srv->dot_stack.size; i++) {
+ free(srv->dot_stack.ptr[i]);
+ }
+ free(srv->dot_stack.ptr);
+
+ free(srv);
+}
+
+static void show_version (void) {
+#ifdef USE_OPENSSL
+# define TEXT_SSL " (ssl)"
+#else
+# define TEXT_SSL
+#endif
+ char *b = PACKAGE "-" VERSION TEXT_SSL \
+" - a light and fast webserver\n" \
+"Build-Date: " __DATE__ " " __TIME__ "\n";
+;
+#undef TEXT_SSL
+ write(STDOUT_FILENO, b, strlen(b));
+}
+
+static void show_help (void) {
+#ifdef USE_OPENSSL
+# define TEXT_SSL " (ssl)"
+#else
+# define TEXT_SSL
+#endif
+#ifdef HAVE_IPV6
+# define TEXT_IPV6 " -6 use IPv6\n"
+#else
+# define TEXT_IPV6
+#endif
+ char *b = PACKAGE "-" VERSION TEXT_SSL " ("__DATE__ " " __TIME__ ")" \
+" - a light and fast webserver\n" \
+"usage:\n" \
+" -f <name> filename of the config-file\n" \
+" -D don't go to background (default: go to background)\n" \
+TEXT_IPV6 \
+" -v show version\n" \
+" -h show this help\n" \
+"\n"
+;
+#undef TEXT_SSL
+#undef TEXT_IPV6
+ write(STDOUT_FILENO, b, strlen(b));
+}
+
+int main (int argc, char **argv) {
+ server *srv = NULL;
+ int i_am_root;
+ int o;
+ int num_childs = 0;
+ int pid_fd = -1, fd;
+ size_t i;
+#ifdef HAVE_SIGACTION
+ struct sigaction act;
+#endif
+#ifdef HAVE_GETRLIMIT
+ struct rlimit rlim;
+#endif
+
+#ifdef USE_ALARM
+ struct itimerval interval;
+
+ interval.it_interval.tv_sec = 1;
+ interval.it_interval.tv_usec = 0;
+ interval.it_value.tv_sec = 1;
+ interval.it_value.tv_usec = 0;
+#endif
+
+
+ /* for nice %b handling in strfime() */
+ setlocale(LC_TIME, "C");
+
+ if (NULL == (srv = server_init())) {
+ fprintf(stderr, "did this really happend ?\n");
+ return -1;
+ }
+
+ /* init structs done */
+
+ srv->srvconf.port = 0;
+#ifdef HAVE_GETUID
+ i_am_root = (getuid() == 0);
+#else
+ i_am_root = 0;
+#endif
+ srv->srvconf.dont_daemonize = 0;
+
+ while(-1 != (o = getopt(argc, argv, "f:hvD"))) {
+ switch(o) {
+ case 'f':
+ if (config_read(srv, optarg)) {
+ server_free(srv);
+ return -1;
+ }
+ break;
+ case 'D': srv->srvconf.dont_daemonize = 1; break;
+ case 'v': show_version(); return 0;
+ case 'h': show_help(); return 0;
+ default:
+ show_help();
+ server_free(srv);
+ return -1;
+ }
+ }
+
+ if (!srv->config_storage) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "No configuration available. Try using -f option.");
+
+ server_free(srv);
+ return -1;
+ }
+
+ /* close stdin and stdout, as they are not needed */
+ /* move stdin to /dev/null */
+ if (-1 != (fd = open("/dev/null", O_RDONLY))) {
+ close(STDIN_FILENO);
+ dup2(fd, STDIN_FILENO);
+ close(fd);
+ }
+
+ /* move stdout to /dev/null */
+ if (-1 != (fd = open("/dev/null", O_WRONLY))) {
+ close(STDOUT_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ close(fd);
+ }
+
+ if (0 != config_set_defaults(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "setting default values failed");
+ server_free(srv);
+ return -1;
+ }
+
+ /* UID handling */
+#ifdef HAVE_GETUID
+ if (!i_am_root && (geteuid() == 0 || getegid() == 0)) {
+ /* we are setuid-root */
+
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "Are you nuts ? Don't apply a SUID bit to this binary");
+
+ server_free(srv);
+ return -1;
+ }
+#endif
+
+ /* check document-root */
+ if (srv->config_storage[0]->document_root->used <= 1) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "document-root is not set\n");
+
+ server_free(srv);
+
+ return -1;
+ }
+
+ if (plugins_load(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "loading plugins finally failed");
+
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* open pid file BEFORE chroot */
+ if (srv->srvconf.pid_file->used) {
+ if (-1 == (pid_fd = open(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
+ struct stat st;
+ if (errno != EEXIST) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno));
+ return -1;
+ }
+
+ if (0 != stat(srv->srvconf.pid_file->ptr, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "stating existing pid-file failed:", srv->srvconf.pid_file, strerror(errno));
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "pid-file exists and isn't regular file:", srv->srvconf.pid_file);
+ return -1;
+ }
+
+ if (-1 == (pid_fd = open(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno));
+ return -1;
+ }
+ }
+ }
+
+ if (i_am_root) {
+ struct group *grp = NULL;
+ struct passwd *pwd = NULL;
+ int use_rlimit = 1;
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ if (RUNNING_ON_VALGRIND) use_rlimit = 0;
+#endif
+
+#ifdef HAVE_GETRLIMIT
+ if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ss", "couldn't get 'max filedescriptors'",
+ strerror(errno));
+ return -1;
+ }
+
+ if (use_rlimit && srv->srvconf.max_fds) {
+ /* set rlimits */
+
+ rlim.rlim_cur = srv->srvconf.max_fds;
+ rlim.rlim_max = srv->srvconf.max_fds;
+
+ if (0 != setrlimit(RLIMIT_NOFILE, &rlim)) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ss", "couldn't set 'max filedescriptors'",
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ srv->max_fds = rlim.rlim_cur;
+#else
+ srv->max_fds = 4096;
+#endif
+
+ if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "s", "fdevent_init failed");
+ return -1;
+ }
+
+#ifdef HAVE_PWD_H
+ /* set user and group */
+ if (srv->srvconf.username->used) {
+ if (NULL == (pwd = getpwnam(srv->srvconf.username->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "can't find username", srv->srvconf.username);
+ return -1;
+ }
+
+ if (pwd->pw_uid == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "I will not set uid to 0\n");
+ return -1;
+ }
+ }
+
+ if (srv->srvconf.groupname->used) {
+ if (NULL == (grp = getgrnam(srv->srvconf.groupname->ptr))) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "can't find groupname", srv->srvconf.groupname);
+ return -1;
+ }
+ if (grp->gr_gid == 0) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "I will not set gid to 0\n");
+ return -1;
+ }
+ }
+#endif
+ /* we need root-perms for port < 1024 */
+ if (0 != network_init(srv)) {
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+#ifdef HAVE_CHROOT
+ if (srv->srvconf.changeroot->used) {
+ tzset();
+
+ if (-1 == chroot(srv->srvconf.changeroot->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "chroot failed: ", strerror(errno));
+ return -1;
+ }
+ if (-1 == chdir("/")) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "chdir failed: ", strerror(errno));
+ return -1;
+ }
+ }
+#endif
+#ifdef HAVE_PWD_H
+ /* drop root privs */
+ if (srv->srvconf.groupname->used) {
+ setgid(grp->gr_gid);
+ setgroups(0, NULL);
+ }
+ if (srv->srvconf.username->used) setuid(pwd->pw_uid);
+#endif
+ } else {
+#ifdef HAVE_GETRLIMIT
+ if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "ss", "couldn't get 'max filedescriptors'",
+ strerror(errno));
+ return -1;
+ }
+
+ srv->max_fds = rlim.rlim_cur;
+#else
+ srv->max_fds = 4096;
+#endif
+
+ if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) {
+ log_error_write(srv, __FILE__, __LINE__,
+ "s", "fdevent_init failed");
+ return -1;
+ }
+
+ if (0 != network_init(srv)) {
+ plugins_free(srv);
+ server_free(srv);
+
+ return -1;
+ }
+ }
+
+ if (HANDLER_GO_ON != plugins_call_init(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Initialization of plugins failed. Going down.");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ if (HANDLER_GO_ON != plugins_call_set_defaults(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "Configuration of plugins failed. Going down.");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* dump unused config-keys */
+ for (i = 0; srv->config && i < srv->config->used; i++) {
+ data_unset *du = srv->config->data[i];
+
+ if (NULL == array_get_element(srv->config_touched, du->key->ptr)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "WARNING: unknown config-key:",
+ du->key,
+ "(ignored)");
+ }
+ }
+
+ if (srv->config_deprecated) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "Configuration contains deprecated keys. Going down.");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+#ifdef HAVE_FORK
+ /* network is up, let's deamonize ourself */
+ if (srv->srvconf.dont_daemonize == 0) daemonize();
+#endif
+
+ /* write pid file */
+ if (pid_fd != -1) {
+ buffer_copy_long(srv->tmp_buf, getpid());
+ buffer_append_string(srv->tmp_buf, "\n");
+ write(pid_fd, srv->tmp_buf->ptr, srv->tmp_buf->used - 1);
+ close(pid_fd);
+ pid_fd = -1;
+ }
+
+ if (-1 == log_error_open(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "opening errorlog failed, dying");
+
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+ return -1;
+ }
+
+ /* kqueue needs a reset AFTER daemonize() */
+ if (0 != network_register_fdevents(srv)) {
+ plugins_free(srv);
+ network_close(srv);
+ server_free(srv);
+
+ return -1;
+ }
+
+ /* get the current number of FDs */
+ srv->cur_fds = open("/dev/null", O_RDONLY);
+ close(srv->cur_fds);
+
+
+#ifdef HAVE_SIGACTION
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, NULL);
+ sigaction(SIGUSR1, &act, NULL);
+# if defined(SA_SIGINFO)
+ act.sa_sigaction = sigaction_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_SIGINFO;
+# else
+ act.sa_handler = signal_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+# endif
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGALRM, &act, NULL);
+
+#elif defined(HAVE_SIGNAL)
+ /* ignore the SIGPIPE from sendfile() */
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGUSR1, SIG_IGN);
+ signal(SIGALRM, signal_handler);
+ signal(SIGTERM, signal_handler);
+ signal(SIGHUP, signal_handler);
+#endif
+
+#ifdef USE_ALARM
+ signal(SIGALRM, signal_handler);
+
+ /* setup periodic timer (1 second) */
+ if (setitimer(ITIMER_REAL, &interval, NULL)) {
+ log_error_write(srv, __FILE__, __LINE__, "setting timer failed");
+ return -1;
+ }
+
+ getitimer(ITIMER_REAL, &interval);
+#endif
+
+#ifdef HAVE_FORK
+ /* start watcher and workers */
+ num_childs = srv->srvconf.max_worker;
+ if (num_childs > 0) {
+ int child = 0;
+ while (!child && !srv_shutdown) {
+ if (num_childs > 0) {
+ switch (fork()) {
+ case -1:
+ return -1;
+ case 0:
+ child = 1;
+ break;
+ default:
+ num_childs--;
+ break;
+ }
+ } else {
+ int status;
+ wait(&status);
+ num_childs++;
+ }
+ }
+ if (!child) return 0;
+ }
+#endif
+
+ /* main-loop */
+ while (!srv_shutdown) {
+ int n;
+ size_t ndx;
+ time_t min_ts;
+
+ if (handle_sig_hup) {
+ handler_t r;
+
+ /* reset notification */
+ handle_sig_hup = 0;
+
+
+ /* cycle logfiles */
+
+ switch(r = plugins_call_handle_sighup(srv)) {
+ case HANDLER_GO_ON:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "sd", "sighup-handler return with an error", r);
+ break;
+ }
+
+ if (-1 == log_error_cycle(srv)) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "cycling errorlog failed, dying");
+
+ return -1;
+ }
+ }
+
+ if (handle_sig_alarm) {
+ /* a new second */
+
+#ifdef USE_ALARM
+ /* reset notification */
+ handle_sig_alarm = 0;
+#endif
+
+ /* get current time */
+ min_ts = time(NULL);
+
+ if (min_ts != srv->cur_ts) {
+ int cs = 0;
+ connections *conns = srv->conns;
+ handler_t r;
+
+ switch(r = plugins_call_handle_trigger(srv)) {
+ case HANDLER_GO_ON:
+ break;
+ case HANDLER_ERROR:
+ log_error_write(srv, __FILE__, __LINE__, "s", "one of the triggers failed");
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+
+ /* trigger waitpid */
+ srv->cur_ts = min_ts;
+
+ /**
+ * check all connections for timeouts
+ *
+ */
+ for (ndx = 0; ndx < conns->used; ndx++) {
+ int changed = 0;
+ connection *con;
+ int t_diff;
+
+ con = conns->ptr[ndx];
+
+ if (con->state == CON_STATE_READ ||
+ con->state == CON_STATE_READ_POST) {
+ if (con->request_count == 1) {
+ if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) {
+ /* time - out */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed - read-timeout:", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ changed = 1;
+ }
+ } else {
+ if (srv->cur_ts - con->read_idle_ts > con->conf.max_keep_alive_idle) {
+ /* time - out */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed - read-timeout:", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ changed = 1;
+ }
+ }
+ }
+
+ if ((con->state == CON_STATE_WRITE) &&
+ (con->write_request_ts != 0)) {
+#if 0
+ if (srv->cur_ts - con->write_request_ts > 60) {
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "connection closed - pre-write-request-timeout:", con->fd, srv->cur_ts - con->write_request_ts);
+ }
+#endif
+
+ if (srv->cur_ts - con->write_request_ts > con->conf.max_write_idle) {
+ /* time - out */
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed - write-request-timeout:", con->fd);
+#endif
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ changed = 1;
+ }
+ }
+ /* we don't like div by zero */
+ if (0 == (t_diff = srv->cur_ts - con->connection_start)) t_diff = 1;
+
+ if (con->traffic_limit_reached &&
+ ((con->bytes_written / t_diff) < con->conf.kbytes_per_second * 1024)) {
+ /* enable connection again */
+ con->traffic_limit_reached = 0;
+
+ changed = 1;
+ }
+
+ if (changed) {
+ connection_state_machine(srv, con);
+ }
+ con->bytes_written_cur_second = 0;
+ *(con->conf.global_bytes_per_second_cnt_ptr) = 0;
+
+#if 0
+ if (cs == 0) {
+ fprintf(stderr, "connection-state: ");
+ cs = 1;
+ }
+
+ fprintf(stderr, "c[%d,%d]: %s ",
+ con->fd,
+ con->fcgi.fd,
+ connection_get_state(con->state));
+#endif
+ }
+
+ if (cs == 1) fprintf(stderr, "\n");
+ }
+ }
+
+ /* handle out of fd condition */
+ if (!srv->sockets_disabled &&
+ srv->cur_fds + srv->want_fds > srv->max_fds * 0.9) {
+
+ /* disable server-fds */
+
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+ fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds");
+
+ srv->sockets_disabled = 1;
+ } else if (srv->sockets_disabled &&
+ srv->cur_fds + srv->want_fds < srv->max_fds * 0.8) {
+
+ for (i = 0; i < srv->srv_sockets.used; i++) {
+ server_socket *srv_socket = srv->srv_sockets.ptr[i];
+ fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets enabled, out-of-fds");
+
+ srv->sockets_disabled = 0;
+ }
+
+ /* we still have some fds to share */
+ if (srv->want_fds) {
+ /* check the fdwaitqueue for waiting fds */
+ int free_fds = srv->max_fds - srv->cur_fds - 16;
+ connection *con;
+
+ for (; free_fds > 0 && NULL != (con = fdwaitqueue_unshift(srv, srv->fdwaitqueue)); free_fds--) {
+ connection_state_machine(srv, con);
+
+ srv->want_fds--;
+ }
+ }
+
+ if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
+ /* n is the number of events */
+ int revents;
+ int fd_ndx;
+#if 0
+ if (n > 0) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "polls:", n);
+ }
+#endif
+ fd_ndx = -1;
+ do {
+ fdevent_handler handler;
+ void *context;
+ handler_t r;
+
+ fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx);
+ revents = fdevent_event_get_revent (srv->ev, fd_ndx);
+ fd = fdevent_event_get_fd (srv->ev, fd_ndx);
+ handler = fdevent_get_handler(srv->ev, fd);
+ context = fdevent_get_context(srv->ev, fd);
+
+ /* connection_handle_fdevent needs a joblist_append */
+#if 0
+ log_error_write(srv, __FILE__, __LINE__, "sdd",
+ "event for", fd, revents);
+#endif
+ switch (r = (*handler)(srv, context, revents)) {
+ case HANDLER_FINISHED:
+ case HANDLER_GO_ON:
+ case HANDLER_WAIT_FOR_EVENT:
+ break;
+ case HANDLER_ERROR:
+ /* should never happen */
+ SEGFAULT();
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+ } while (--n > 0);
+ } else if (n < 0 && errno != EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "fdevent_poll failed:",
+ strerror(errno));
+ }
+
+ for (ndx = 0; ndx < srv->joblist->used; ndx++) {
+ connection *con = srv->joblist->ptr[ndx];
+ handler_t r;
+
+ connection_state_machine(srv, con);
+
+ switch(r = plugins_call_handle_joblist(srv, con)) {
+ case HANDLER_FINISHED:
+ case HANDLER_GO_ON:
+ break;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "d", r);
+ break;
+ }
+
+ con->in_joblist = 0;
+ }
+
+ srv->joblist->used = 0;
+ }
+
+ if (srv->srvconf.pid_file->used &&
+ srv->srvconf.changeroot->used == 0) {
+ if (0 != unlink(srv->srvconf.pid_file->ptr)) {
+ if (errno != EPERM) {
+ log_error_write(srv, __FILE__, __LINE__, "sbds",
+ "unlink failed for:",
+ srv->srvconf.pid_file,
+ errno,
+ strerror(errno));
+ }
+ }
+ }
+
+ /* clean-up */
+ log_error_close(srv);
+ network_close(srv);
+ plugins_free(srv);
+ server_free(srv);
+
+ return 0;
+}
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 00000000..bca2d525
--- /dev/null
+++ b/src/server.h
@@ -0,0 +1,17 @@
+#ifndef _SERVER_H_
+#define _SERVER_H_
+
+#include "base.h"
+
+typedef struct {
+ char *key;
+ char *value;
+} two_strings;
+
+typedef enum { CONFIG_UNSET, CONFIG_DOCUMENT_ROOT } config_var_t;
+
+int config_read(server *srv, const char *fn);
+int config_set_defaults(server *srv);
+buffer *config_get_value_buffer(server *srv, connection *con, config_var_t field);
+
+#endif
diff --git a/src/settings.h b/src/settings.h
new file mode 100644
index 00000000..959edcc9
--- /dev/null
+++ b/src/settings.h
@@ -0,0 +1,46 @@
+#ifndef _LIGHTTPD_SETTINGS_H_
+#define _LIGHTTPD_SETTINGS_H_
+
+#define BV(x) (1 << x)
+
+#define INET_NTOP_CACHE_MAX 4
+#define FILE_CACHE_MAX 16
+
+#define FCGI_RETRY_TIMEOUT (5 * 60)
+
+/**
+ * max size of a buffer which will just be reset
+ * to ->used = 0 instead of really freeing the buffer
+ *
+ * 64kB (no real reason, just a guess)
+ */
+#define BUFFER_MAX_REUSE_SIZE (4 * 1024)
+
+/**
+ * max size of the HTTP request header
+ *
+ * 32k should be enough for everything (just a guess)
+ *
+ */
+#define MAX_HTTP_REQUEST_HEADER (32 * 1024)
+
+typedef enum { HANDLER_UNSET,
+ HANDLER_GO_ON,
+ HANDLER_FINISHED,
+ HANDLER_COMEBACK,
+ HANDLER_WAIT_FOR_EVENT,
+ HANDLER_ERROR,
+ HANDLER_WAIT_FOR_FD
+} handler_t;
+
+
+/* we use it in a enum */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#endif
diff --git a/src/spawn-fcgi.c b/src/spawn-fcgi.c
new file mode 100644
index 00000000..8be65cec
--- /dev/null
+++ b/src/spawn-fcgi.c
@@ -0,0 +1,346 @@
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "config.h"
+
+#ifdef HAVE_PWD_H
+#include <grp.h>
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#define FCGI_LISTENSOCK_FILENO 0
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX 108
+#endif
+
+#include "sys-socket.h"
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+/* for solaris 2.5 and netbsd 1.3.x */
+#ifndef HAVE_SOCKLEN_T
+typedef int socklen_t;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+int fcgi_spawn_connection(char *appPath, unsigned short port, const char *unixsocket, int child_count) {
+ int fcgi_fd;
+ int socket_type, status;
+ struct timeval tv = { 0, 100 * 1000 };
+
+ struct sockaddr_un fcgi_addr_un;
+ struct sockaddr_in fcgi_addr_in;
+ struct sockaddr *fcgi_addr;
+
+ socklen_t servlen;
+
+ if (child_count < 2) {
+ child_count = 5;
+ }
+
+ if (child_count > 256) {
+ child_count = 256;
+ }
+
+
+ if (unixsocket) {
+ memset(&fcgi_addr, 0, sizeof(fcgi_addr));
+
+ fcgi_addr_un.sun_family = AF_UNIX;
+ strcpy(fcgi_addr_un.sun_path, unixsocket);
+
+#ifdef SUN_LEN
+ servlen = SUN_LEN(&fcgi_addr_un);
+#else
+ /* stevens says: */
+ servlen = strlen(fcgi_addr_un.sun_path) + sizeof(fcgi_addr_un.sun_family);
+#endif
+ socket_type = AF_UNIX;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_un;
+ } else {
+ fcgi_addr_in.sin_family = AF_INET;
+ fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ fcgi_addr_in.sin_port = htons(port);
+ servlen = sizeof(fcgi_addr_in);
+
+ socket_type = AF_INET;
+ fcgi_addr = (struct sockaddr *) &fcgi_addr_in;
+ }
+
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ fprintf(stderr, "%s.%d\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) {
+ /* server is not up, spawn in */
+ pid_t child;
+
+ if (unixsocket) unlink(unixsocket);
+
+ close(fcgi_fd);
+
+ /* reopen socket */
+ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) {
+ fprintf(stderr, "%s.%d\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ /* create socket */
+ if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) {
+ fprintf(stderr, "%s.%d: bind failed: %s\n",
+ __FILE__, __LINE__,
+ strerror(errno));
+ return -1;
+ }
+
+ if (-1 == listen(fcgi_fd, 1024)) {
+ fprintf(stderr, "%s.%d: fd = -1\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ switch ((child = fork())) {
+ case 0: {
+ char cgi_childs[64];
+
+ int i = 0;
+
+ /* is save as we limit to 256 childs */
+ sprintf(cgi_childs, "PHP_FCGI_CHILDREN=%d", child_count);
+
+ if(fcgi_fd != FCGI_LISTENSOCK_FILENO) {
+ close(FCGI_LISTENSOCK_FILENO);
+ dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO);
+ close(fcgi_fd);
+ }
+
+ /* we don't need the client socket */
+ for (i = 3; i < 256; i++) {
+ close(i);
+ }
+
+ /* create environment */
+
+ putenv(cgi_childs);
+
+ /* exec the cgi */
+ execl("/bin/sh", "sh", "-c", appPath, NULL);
+
+ exit(errno);
+
+ break;
+ }
+ case -1:
+ /* error */
+ break;
+ default:
+ /* father */
+
+ /* wait */
+ select(0, NULL, NULL, NULL, &tv);
+
+ switch (waitpid(child, &status, WNOHANG)) {
+ case 0:
+ fprintf(stderr, "%s.%d: child spawned successfully: PID: %d\n",
+ __FILE__, __LINE__,
+ child);
+ break;
+ case -1:
+ break;
+ default:
+ if (WIFEXITED(status)) {
+ fprintf(stderr, "%s.%d: child exited with: %d, %s\n",
+ __FILE__, __LINE__,
+ WEXITSTATUS(status), strerror(WEXITSTATUS(status)));
+ } else if (WIFSIGNALED(status)) {
+ fprintf(stderr, "%s.%d: child signaled: %d\n",
+ __FILE__, __LINE__,
+ WTERMSIG(status));
+ } else {
+ fprintf(stderr, "%s.%d: child died somehow: %d\n",
+ __FILE__, __LINE__,
+ status);
+ }
+ }
+
+ break;
+ }
+ } else {
+ fprintf(stderr, "%s.%d: socket is already used, can't spawn\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ close(fcgi_fd);
+
+ return 0;
+}
+
+
+void show_version () {
+ char *b = "spawn-fcgi" "-" VERSION \
+" - spawns fastcgi processes\n" \
+"Build-Date: " __DATE__ " " __TIME__ "\n";
+;
+ write(1, b, strlen(b));
+}
+
+void show_help () {
+ char *b = "spawn-fcgi" "-" VERSION \
+" - spawns fastcgi processes\n" \
+"usage:\n" \
+" -f <fcgiapp> filename of the fcgi-application\n" \
+" -p <port> bind to tcp-port\n" \
+" -s <path> bind to unix-domain socket\n" \
+" -C <childs> (PHP only) numbers of childs to spawn (default 5)\n" \
+" -v show version\n" \
+" -h show this help\n" \
+"(root only)\n" \
+" -c <dir> chroot to directory\n" \
+" -u <user> change to user-id\n" \
+" -g <group> change to group-id\n" \
+;
+ write(1, b, strlen(b));
+}
+
+
+int main(int argc, char **argv) {
+ char *fcgi_app = NULL, *changeroot = NULL, *username = NULL, *groupname = NULL, *unixsocket = NULL;
+ unsigned short port = 0;
+ int child_count = 5;
+ int i_am_root, o;
+
+ i_am_root = (getuid() == 0);
+
+ while(-1 != (o = getopt(argc, argv, "c:f:g:hp:u:vC:s:"))) {
+ switch(o) {
+ case 'f': fcgi_app = optarg; break;
+ case 'p': port = strtol(optarg, NULL, 10);/* port */ break;
+ case 'C': child_count = strtol(optarg, NULL, 10);/* */ break;
+ case 's': unixsocket = optarg; /* unix-domain socket */ break;
+ case 'c': if (i_am_root) { changeroot = optarg; }/* chroot() */ break;
+ case 'u': if (i_am_root) { username = optarg; } /* set user */ break;
+ case 'g': if (i_am_root) { groupname = optarg; } /* set group */ break;
+ case 'v': show_version(); return 0;
+ case 'h': show_help(); return 0;
+ default:
+ show_help();
+ return -1;
+ }
+ }
+
+ if (fcgi_app == NULL || (port == 0 && unixsocket == NULL)) {
+ show_help();
+ return -1;
+ }
+
+ if (unixsocket && port) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "either a unix domain socket or a tcp-port, but not both\n");
+
+ return -1;
+ }
+
+ if (unixsocket && strlen(unixsocket) > UNIX_PATH_MAX - 1) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "path of the unix socket is too long\n");
+
+ return -1;
+ }
+
+ /* UID handling */
+ if (!i_am_root && (geteuid() == 0 || getegid() == 0)) {
+ /* we are setuid-root */
+
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "Are you nuts ? Don't apply a SUID bit to this binary\n");
+
+ return -1;
+ }
+
+ if (i_am_root) {
+ struct group *grp = NULL;
+ struct passwd *pwd = NULL;
+
+ /* set user and group */
+
+ if (username) {
+ if (NULL == (pwd = getpwnam(username))) {
+ fprintf(stderr, "%s.%d: %s, %s\n",
+ __FILE__, __LINE__,
+ "can't find username", username);
+ return -1;
+ }
+
+ if (pwd->pw_uid == 0) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "I will not set uid to 0\n");
+ return -1;
+ }
+ }
+
+ if (groupname) {
+ if (NULL == (grp = getgrnam(groupname))) {
+ fprintf(stderr, "%s.%d: %s %s\n",
+ __FILE__, __LINE__,
+ "can't find groupname",
+ groupname);
+ return -1;
+ }
+ if (grp->gr_gid == 0) {
+ fprintf(stderr, "%s.%d: %s\n",
+ __FILE__, __LINE__,
+ "I will not set gid to 0\n");
+ return -1;
+ }
+ }
+
+ if (changeroot) {
+ if (-1 == chroot(changeroot)) {
+ fprintf(stderr, "%s.%d: %s %s\n",
+ __FILE__, __LINE__,
+ "chroot failed: ", strerror(errno));
+ return -1;
+ }
+ if (-1 == chdir("/")) {
+ fprintf(stderr, "%s.%d: %s %s\n",
+ __FILE__, __LINE__,
+ "chdir failed: ", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* drop root privs */
+ if (groupname) {
+ setgid(grp->gr_gid);
+ setgroups(0, NULL);
+ }
+ if (username) setuid(pwd->pw_uid);
+ }
+
+ return fcgi_spawn_connection(fcgi_app, port, unixsocket, child_count);
+}
+#else
+int main() {
+ return -1;
+}
+#endif
diff --git a/src/stream.c b/src/stream.c
new file mode 100644
index 00000000..ec228d85
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,100 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "stream.h"
+#include "config.h"
+
+#include "sys-mmap.h"
+
+
+int stream_open(stream *f, buffer *fn) {
+ struct stat st;
+#ifdef HAVE_MMAP
+ int fd;
+#elif defined __WIN32
+ HANDLE *fh, *mh;
+ void *p;
+#endif
+
+
+ if (-1 == stat(fn->ptr, &st)) {
+ return -1;
+ }
+
+ f->size = st.st_size;
+
+#ifdef HAVE_MMAP
+ if (-1 == (fd = open(fn->ptr, O_RDONLY))) {
+ return -1;
+ }
+
+ f->start = mmap(0, f->size, PROT_READ, MAP_SHARED, fd, 0);
+
+ close(fd);
+
+ if (MAP_FAILED == f->start) {
+ return -1;
+ }
+
+#elif defined __WIN32
+ fh = CreateFile(fn->ptr,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_READONLY,
+ NULL);
+
+ if (!fh) return -1;
+
+ mh = CreateFileMapping( fh,
+ NULL,
+ PAGE_READONLY,
+ (sizeof(off_t) > 4) ? f->size >> 32 : 0,
+ f->size & 0xffffffff,
+ NULL);
+
+ if (!mh) {
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ return -1;
+ }
+
+ p = MapViewOfFile(mh,
+ FILE_MAP_READ,
+ 0,
+ 0,
+ 0);
+ CloseHandle(mh);
+ CloseHandle(fh);
+
+ f->start = p;
+#else
+# error no mmap found
+#endif
+
+ return 0;
+}
+
+int stream_close(stream *f) {
+#ifdef HAVE_MMAP
+ if (f->start) munmap(f->start, f->size);
+#elif defined(__WIN32)
+ if (f->start) UnmapViewOfFile(f->start);
+#endif
+
+ f->start = NULL;
+
+ return 0;
+}
diff --git a/src/stream.h b/src/stream.h
new file mode 100644
index 00000000..d4c9049b
--- /dev/null
+++ b/src/stream.h
@@ -0,0 +1,14 @@
+#ifndef _STREAM_H_
+#define _STREAM_H_
+
+#include "buffer.h"
+
+typedef struct {
+ char *start;
+ off_t size;
+} stream;
+
+int stream_open(stream *f, buffer *fn);
+int stream_close(stream *f);
+
+#endif
diff --git a/src/sys-mmap.h b/src/sys-mmap.h
new file mode 100644
index 00000000..94aaa19b
--- /dev/null
+++ b/src/sys-mmap.h
@@ -0,0 +1,24 @@
+#ifndef WIN32_MMAP_H
+#define WIN32_MMAP_H
+
+#ifdef __WIN32
+
+#define MAP_FAILED -1
+#define PROT_SHARED 0
+#define MAP_SHARED 0
+#define PROT_READ 0
+
+#define mmap(a, b, c, d, e, f) (-1)
+#define munmap(a, b) (-1)
+
+#include <windows.h>
+
+#else
+#include <sys/mman.h>
+
+#ifndef MAP_FAILED
+#define MAP_FAILED -1
+#endif
+#endif
+
+#endif
diff --git a/src/sys-socket.h b/src/sys-socket.h
new file mode 100644
index 00000000..cc6e649b
--- /dev/null
+++ b/src/sys-socket.h
@@ -0,0 +1,24 @@
+#ifndef WIN32_SOCKET_H
+#define WIN32_SOCKET_H
+
+#ifdef __WIN32
+
+#include <winsock2.h>
+
+#define ECONNRESET WSAECONNRESET
+#define EINPROGRESS WSAEINPROGRESS
+#define EALREADY WSAEALREADY
+#define ioctl ioctlsocket
+#define hstrerror(x) ""
+#else
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#endif
+
+#endif