diff options
author | Arno Töll <arno@debian.org> | 2012-11-21 23:03:34 +0100 |
---|---|---|
committer | Arno Töll <arno@debian.org> | 2012-11-21 23:03:34 +0100 |
commit | eb45c46b906e492f063f1469486190e93ff340ff (patch) | |
tree | 85d615969fa7bf8056a05b59006f77bc63e85892 /src | |
parent | 6426b37107707a1d95ffd03f68620cbda8bdb942 (diff) | |
download | lighttpd-eb45c46b906e492f063f1469486190e93ff340ff.tar.gz |
Imported Upstream version 1.4.10upstream/1.4.10
Diffstat (limited to 'src')
128 files changed, 53001 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..7e9fc9e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,264 @@ +noinst_PROGRAMS=proc_open 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 + +configfile.c: configparser.h +mod_ssi_expr.c: mod_ssi_exprparser.h + +common_src=buffer.c log.c \ + keyvalue.c chunk.c \ + http_chunk.c stream.c fdevent.c \ + stat_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 \ + data_config.c bitset.c \ + inet_ntop_cache.c crc32.c \ + connections-glue.c \ + configfile-glue.c \ + http-header-glue.c \ + network_write.c network_linux_sendfile.c \ + network_freebsd_sendfile.c network_writev.c \ + network_solaris_sendfilev.c network_openssl.c \ + splaytree.c + +src = server.c response.c connections.c network.c \ + configfile.c configparser.c request.c proc_open.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) $(FAM_CFLAGS) +liblightcomp_la_LDFLAGS = -avoid-version -no-undefined +liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB) $(FAM_LIBS) +common_libadd = liblightcomp.la +else +src += $(common_src) +common_libadd = +endif + +lib_LTLIBRARIES += mod_evasive.la +mod_evasive_la_SOURCES = mod_evasive.c +mod_evasive_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_evasive_la_LIBADD = $(common_libadd) + + +lib_LTLIBRARIES += mod_webdav.la +mod_webdav_la_SOURCES = mod_webdav.c +mod_webdav_la_CFLAGS = $(XML_CFLAGS) +mod_webdav_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_webdav_la_LIBADD = $(common_libadd) $(XML_LIBS) $(SQLITE_LIBS) + +lib_LTLIBRARIES += mod_cml.la +mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c +mod_cml_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS) +mod_cml_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) $(LUA_LIBS) -lm + +lib_LTLIBRARIES += mod_trigger_b4_dl.la +mod_trigger_b4_dl_la_SOURCES = mod_trigger_b4_dl.c +mod_trigger_b4_dl_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_trigger_b4_dl_la_LIBADD = $(GDBM_LIB) $(MEMCACHE_LIB) $(PCRE_LIB) $(common_libadd) + +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) +mod_mysql_vhost_la_CPPFLAGS = $(MYSQL_INCLUDE) + +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_scgi.la +mod_scgi_la_SOURCES = mod_scgi.c +mod_scgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_scgi_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_staticfile.la +mod_staticfile_la_SOURCES = mod_staticfile.c +mod_staticfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_staticfile_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_dirlisting.la +mod_dirlisting_la_SOURCES = mod_dirlisting.c +mod_dirlisting_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_dirlisting_la_LIBADD = $(common_libadd) $(PCRE_LIB) + +lib_LTLIBRARIES += mod_indexfile.la +mod_indexfile_la_SOURCES = mod_indexfile.c +mod_indexfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_indexfile_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 +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 http_auth.h stream.h \ + fdevent.h connections.h base.h stat_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 mod_cml.h mod_cml_funcs.h \ + splaytree.h proc_open.h + +DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\"" + +lighttpd_SOURCES = $(src) +lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB) $(FAM_LIBS) +lighttpd_LDFLAGS = -export-dynamic +lighttpd_CCPFLAGS = $(FAM_CFLAGS) + +proc_open_SOURCES = proc_open.c buffer.c +proc_open_CPPFLAGS= -DDEBUG_PROC_OPEN + +#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/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..cc6fab9 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,1512 @@ +# Makefile.in generated by automake 1.9.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +SOURCES = $(liblightcomp_la_SOURCES) $(mod_access_la_SOURCES) $(mod_accesslog_la_SOURCES) $(mod_alias_la_SOURCES) $(mod_auth_la_SOURCES) $(mod_cgi_la_SOURCES) $(mod_cml_la_SOURCES) $(mod_compress_la_SOURCES) $(mod_dirlisting_la_SOURCES) $(mod_evasive_la_SOURCES) $(mod_evhost_la_SOURCES) $(mod_expire_la_SOURCES) $(mod_fastcgi_la_SOURCES) $(mod_indexfile_la_SOURCES) $(mod_mysql_vhost_la_SOURCES) $(mod_proxy_la_SOURCES) $(mod_redirect_la_SOURCES) $(mod_rewrite_la_SOURCES) $(mod_rrdtool_la_SOURCES) $(mod_scgi_la_SOURCES) $(mod_secdownload_la_SOURCES) $(mod_setenv_la_SOURCES) $(mod_simple_vhost_la_SOURCES) $(mod_ssi_la_SOURCES) $(mod_staticfile_la_SOURCES) $(mod_status_la_SOURCES) $(mod_trigger_b4_dl_la_SOURCES) $(mod_userdir_la_SOURCES) $(mod_usertrack_la_SOURCES) $(mod_webdav_la_SOURCES) $(lemon_SOURCES) $(lighttpd_SOURCES) $(proc_open_SOURCES) $(spawn_fcgi_SOURCES) + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +noinst_PROGRAMS = proc_open$(EXEEXT) lemon$(EXEEXT) +sbin_PROGRAMS = lighttpd$(EXEEXT) +bin_PROGRAMS = spawn-fcgi$(EXEEXT) + +# 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 +@NO_RDYNAMIC_TRUE@am__append_1 = liblightcomp.la +@NO_RDYNAMIC_FALSE@am__append_2 = $(common_src) +subdir = src +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \ + "$(DESTDIR)$(sbindir)" +libLTLIBRARIES_INSTALL = $(INSTALL) +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +@NO_RDYNAMIC_TRUE@liblightcomp_la_DEPENDENCIES = \ +@NO_RDYNAMIC_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@NO_RDYNAMIC_TRUE@ $(am__DEPENDENCIES_1) +am__liblightcomp_la_SOURCES_DIST = buffer.c log.c keyvalue.c chunk.c \ + http_chunk.c stream.c fdevent.c stat_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 data_config.c bitset.c \ + inet_ntop_cache.c crc32.c connections-glue.c configfile-glue.c \ + http-header-glue.c network_write.c network_linux_sendfile.c \ + network_freebsd_sendfile.c network_writev.c \ + network_solaris_sendfilev.c network_openssl.c splaytree.c +am__objects_1 = liblightcomp_la-buffer.lo liblightcomp_la-log.lo \ + liblightcomp_la-keyvalue.lo liblightcomp_la-chunk.lo \ + liblightcomp_la-http_chunk.lo liblightcomp_la-stream.lo \ + liblightcomp_la-fdevent.lo liblightcomp_la-stat_cache.lo \ + liblightcomp_la-plugin.lo liblightcomp_la-joblist.lo \ + liblightcomp_la-etag.lo liblightcomp_la-array.lo \ + liblightcomp_la-data_string.lo liblightcomp_la-data_count.lo \ + liblightcomp_la-data_array.lo liblightcomp_la-data_integer.lo \ + liblightcomp_la-md5.lo liblightcomp_la-data_fastcgi.lo \ + liblightcomp_la-fdevent_select.lo \ + liblightcomp_la-fdevent_linux_rtsig.lo \ + liblightcomp_la-fdevent_poll.lo \ + liblightcomp_la-fdevent_linux_sysepoll.lo \ + liblightcomp_la-fdevent_solaris_devpoll.lo \ + liblightcomp_la-fdevent_freebsd_kqueue.lo \ + liblightcomp_la-data_config.lo liblightcomp_la-bitset.lo \ + liblightcomp_la-inet_ntop_cache.lo liblightcomp_la-crc32.lo \ + liblightcomp_la-connections-glue.lo \ + liblightcomp_la-configfile-glue.lo \ + liblightcomp_la-http-header-glue.lo \ + liblightcomp_la-network_write.lo \ + liblightcomp_la-network_linux_sendfile.lo \ + liblightcomp_la-network_freebsd_sendfile.lo \ + liblightcomp_la-network_writev.lo \ + liblightcomp_la-network_solaris_sendfilev.lo \ + liblightcomp_la-network_openssl.lo \ + liblightcomp_la-splaytree.lo +@NO_RDYNAMIC_TRUE@am_liblightcomp_la_OBJECTS = $(am__objects_1) +liblightcomp_la_OBJECTS = $(am_liblightcomp_la_OBJECTS) +@NO_RDYNAMIC_TRUE@am_liblightcomp_la_rpath = -rpath $(libdir) +@NO_RDYNAMIC_TRUE@am__DEPENDENCIES_2 = liblightcomp.la +mod_access_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_access_la_OBJECTS = mod_access.lo +mod_access_la_OBJECTS = $(am_mod_access_la_OBJECTS) +mod_accesslog_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_accesslog_la_OBJECTS = mod_accesslog.lo +mod_accesslog_la_OBJECTS = $(am_mod_accesslog_la_OBJECTS) +mod_alias_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_alias_la_OBJECTS = mod_alias.lo +mod_alias_la_OBJECTS = $(am_mod_alias_la_OBJECTS) +mod_auth_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) +am_mod_auth_la_OBJECTS = mod_auth.lo http_auth_digest.lo http_auth.lo +mod_auth_la_OBJECTS = $(am_mod_auth_la_OBJECTS) +mod_cgi_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_cgi_la_OBJECTS = mod_cgi.lo +mod_cgi_la_OBJECTS = $(am_mod_cgi_la_OBJECTS) +mod_cml_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_1) +am_mod_cml_la_OBJECTS = mod_cml_la-mod_cml.lo \ + mod_cml_la-mod_cml_lua.lo mod_cml_la-mod_cml_funcs.lo +mod_cml_la_OBJECTS = $(am_mod_cml_la_OBJECTS) +mod_compress_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) +am_mod_compress_la_OBJECTS = mod_compress.lo +mod_compress_la_OBJECTS = $(am_mod_compress_la_OBJECTS) +mod_dirlisting_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_1) +am_mod_dirlisting_la_OBJECTS = mod_dirlisting.lo +mod_dirlisting_la_OBJECTS = $(am_mod_dirlisting_la_OBJECTS) +mod_evasive_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_evasive_la_OBJECTS = mod_evasive.lo +mod_evasive_la_OBJECTS = $(am_mod_evasive_la_OBJECTS) +mod_evhost_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_evhost_la_OBJECTS = mod_evhost.lo +mod_evhost_la_OBJECTS = $(am_mod_evhost_la_OBJECTS) +mod_expire_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_expire_la_OBJECTS = mod_expire.lo +mod_expire_la_OBJECTS = $(am_mod_expire_la_OBJECTS) +mod_fastcgi_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_fastcgi_la_OBJECTS = mod_fastcgi.lo +mod_fastcgi_la_OBJECTS = $(am_mod_fastcgi_la_OBJECTS) +mod_indexfile_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_indexfile_la_OBJECTS = mod_indexfile.lo +mod_indexfile_la_OBJECTS = $(am_mod_indexfile_la_OBJECTS) +mod_mysql_vhost_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) +am_mod_mysql_vhost_la_OBJECTS = mod_mysql_vhost_la-mod_mysql_vhost.lo +mod_mysql_vhost_la_OBJECTS = $(am_mod_mysql_vhost_la_OBJECTS) +mod_proxy_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_proxy_la_OBJECTS = mod_proxy.lo +mod_proxy_la_OBJECTS = $(am_mod_proxy_la_OBJECTS) +mod_redirect_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) +am_mod_redirect_la_OBJECTS = mod_redirect.lo +mod_redirect_la_OBJECTS = $(am_mod_redirect_la_OBJECTS) +mod_rewrite_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) +am_mod_rewrite_la_OBJECTS = mod_rewrite.lo +mod_rewrite_la_OBJECTS = $(am_mod_rewrite_la_OBJECTS) +mod_rrdtool_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_rrdtool_la_OBJECTS = mod_rrdtool.lo +mod_rrdtool_la_OBJECTS = $(am_mod_rrdtool_la_OBJECTS) +mod_scgi_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_scgi_la_OBJECTS = mod_scgi.lo +mod_scgi_la_OBJECTS = $(am_mod_scgi_la_OBJECTS) +mod_secdownload_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_secdownload_la_OBJECTS = mod_secure_download.lo +mod_secdownload_la_OBJECTS = $(am_mod_secdownload_la_OBJECTS) +mod_setenv_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_setenv_la_OBJECTS = mod_setenv.lo +mod_setenv_la_OBJECTS = $(am_mod_setenv_la_OBJECTS) +mod_simple_vhost_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_simple_vhost_la_OBJECTS = mod_simple_vhost.lo +mod_simple_vhost_la_OBJECTS = $(am_mod_simple_vhost_la_OBJECTS) +mod_ssi_la_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +am_mod_ssi_la_OBJECTS = mod_ssi_exprparser.lo mod_ssi_expr.lo \ + mod_ssi.lo +mod_ssi_la_OBJECTS = $(am_mod_ssi_la_OBJECTS) +mod_staticfile_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_staticfile_la_OBJECTS = mod_staticfile.lo +mod_staticfile_la_OBJECTS = $(am_mod_staticfile_la_OBJECTS) +mod_status_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_status_la_OBJECTS = mod_status.lo +mod_status_la_OBJECTS = $(am_mod_status_la_OBJECTS) +mod_trigger_b4_dl_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) +am_mod_trigger_b4_dl_la_OBJECTS = mod_trigger_b4_dl.lo +mod_trigger_b4_dl_la_OBJECTS = $(am_mod_trigger_b4_dl_la_OBJECTS) +mod_userdir_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_userdir_la_OBJECTS = mod_userdir.lo +mod_userdir_la_OBJECTS = $(am_mod_userdir_la_OBJECTS) +mod_usertrack_la_DEPENDENCIES = $(am__DEPENDENCIES_2) +am_mod_usertrack_la_OBJECTS = mod_usertrack.lo +mod_usertrack_la_OBJECTS = $(am_mod_usertrack_la_OBJECTS) +mod_webdav_la_DEPENDENCIES = $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_mod_webdav_la_OBJECTS = mod_webdav_la-mod_webdav.lo +mod_webdav_la_OBJECTS = $(am_mod_webdav_la_OBJECTS) +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +sbinPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) $(sbin_PROGRAMS) +am_lemon_OBJECTS = lemon.$(OBJEXT) +lemon_OBJECTS = $(am_lemon_OBJECTS) +lemon_LDADD = $(LDADD) +am__lighttpd_SOURCES_DIST = server.c response.c connections.c \ + network.c configfile.c configparser.c request.c proc_open.c \ + buffer.c log.c keyvalue.c chunk.c http_chunk.c stream.c \ + fdevent.c stat_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 \ + data_config.c bitset.c inet_ntop_cache.c crc32.c \ + connections-glue.c configfile-glue.c http-header-glue.c \ + network_write.c network_linux_sendfile.c \ + network_freebsd_sendfile.c network_writev.c \ + network_solaris_sendfilev.c network_openssl.c splaytree.c +am__objects_2 = buffer.$(OBJEXT) log.$(OBJEXT) keyvalue.$(OBJEXT) \ + chunk.$(OBJEXT) http_chunk.$(OBJEXT) stream.$(OBJEXT) \ + fdevent.$(OBJEXT) stat_cache.$(OBJEXT) plugin.$(OBJEXT) \ + joblist.$(OBJEXT) etag.$(OBJEXT) array.$(OBJEXT) \ + data_string.$(OBJEXT) data_count.$(OBJEXT) \ + data_array.$(OBJEXT) data_integer.$(OBJEXT) md5.$(OBJEXT) \ + data_fastcgi.$(OBJEXT) fdevent_select.$(OBJEXT) \ + fdevent_linux_rtsig.$(OBJEXT) fdevent_poll.$(OBJEXT) \ + fdevent_linux_sysepoll.$(OBJEXT) \ + fdevent_solaris_devpoll.$(OBJEXT) \ + fdevent_freebsd_kqueue.$(OBJEXT) data_config.$(OBJEXT) \ + bitset.$(OBJEXT) inet_ntop_cache.$(OBJEXT) crc32.$(OBJEXT) \ + connections-glue.$(OBJEXT) configfile-glue.$(OBJEXT) \ + http-header-glue.$(OBJEXT) network_write.$(OBJEXT) \ + network_linux_sendfile.$(OBJEXT) \ + network_freebsd_sendfile.$(OBJEXT) network_writev.$(OBJEXT) \ + network_solaris_sendfilev.$(OBJEXT) network_openssl.$(OBJEXT) \ + splaytree.$(OBJEXT) +@NO_RDYNAMIC_FALSE@am__objects_3 = $(am__objects_2) +am__objects_4 = server.$(OBJEXT) response.$(OBJEXT) \ + connections.$(OBJEXT) network.$(OBJEXT) configfile.$(OBJEXT) \ + configparser.$(OBJEXT) request.$(OBJEXT) proc_open.$(OBJEXT) \ + $(am__objects_3) +am_lighttpd_OBJECTS = $(am__objects_4) +lighttpd_OBJECTS = $(am_lighttpd_OBJECTS) +lighttpd_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_proc_open_OBJECTS = proc_open-proc_open.$(OBJEXT) \ + proc_open-buffer.$(OBJEXT) +proc_open_OBJECTS = $(am_proc_open_OBJECTS) +proc_open_LDADD = $(LDADD) +am_spawn_fcgi_OBJECTS = spawn-fcgi.$(OBJEXT) +spawn_fcgi_OBJECTS = $(am_spawn_fcgi_OBJECTS) +spawn_fcgi_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(liblightcomp_la_SOURCES) $(mod_access_la_SOURCES) \ + $(mod_accesslog_la_SOURCES) $(mod_alias_la_SOURCES) \ + $(mod_auth_la_SOURCES) $(mod_cgi_la_SOURCES) \ + $(mod_cml_la_SOURCES) $(mod_compress_la_SOURCES) \ + $(mod_dirlisting_la_SOURCES) $(mod_evasive_la_SOURCES) \ + $(mod_evhost_la_SOURCES) $(mod_expire_la_SOURCES) \ + $(mod_fastcgi_la_SOURCES) $(mod_indexfile_la_SOURCES) \ + $(mod_mysql_vhost_la_SOURCES) $(mod_proxy_la_SOURCES) \ + $(mod_redirect_la_SOURCES) $(mod_rewrite_la_SOURCES) \ + $(mod_rrdtool_la_SOURCES) $(mod_scgi_la_SOURCES) \ + $(mod_secdownload_la_SOURCES) $(mod_setenv_la_SOURCES) \ + $(mod_simple_vhost_la_SOURCES) $(mod_ssi_la_SOURCES) \ + $(mod_staticfile_la_SOURCES) $(mod_status_la_SOURCES) \ + $(mod_trigger_b4_dl_la_SOURCES) $(mod_userdir_la_SOURCES) \ + $(mod_usertrack_la_SOURCES) $(mod_webdav_la_SOURCES) \ + $(lemon_SOURCES) $(lighttpd_SOURCES) $(proc_open_SOURCES) \ + $(spawn_fcgi_SOURCES) +DIST_SOURCES = $(am__liblightcomp_la_SOURCES_DIST) \ + $(mod_access_la_SOURCES) $(mod_accesslog_la_SOURCES) \ + $(mod_alias_la_SOURCES) $(mod_auth_la_SOURCES) \ + $(mod_cgi_la_SOURCES) $(mod_cml_la_SOURCES) \ + $(mod_compress_la_SOURCES) $(mod_dirlisting_la_SOURCES) \ + $(mod_evasive_la_SOURCES) $(mod_evhost_la_SOURCES) \ + $(mod_expire_la_SOURCES) $(mod_fastcgi_la_SOURCES) \ + $(mod_indexfile_la_SOURCES) $(mod_mysql_vhost_la_SOURCES) \ + $(mod_proxy_la_SOURCES) $(mod_redirect_la_SOURCES) \ + $(mod_rewrite_la_SOURCES) $(mod_rrdtool_la_SOURCES) \ + $(mod_scgi_la_SOURCES) $(mod_secdownload_la_SOURCES) \ + $(mod_setenv_la_SOURCES) $(mod_simple_vhost_la_SOURCES) \ + $(mod_ssi_la_SOURCES) $(mod_staticfile_la_SOURCES) \ + $(mod_status_la_SOURCES) $(mod_trigger_b4_dl_la_SOURCES) \ + $(mod_userdir_la_SOURCES) $(mod_usertrack_la_SOURCES) \ + $(mod_webdav_la_SOURCES) $(lemon_SOURCES) \ + $(am__lighttpd_SOURCES_DIST) $(proc_open_SOURCES) \ + $(spawn_fcgi_SOURCES) +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +ATTR_LIB = @ATTR_LIB@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BZ_LIB = @BZ_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHECK_WITH_FASTCGI_FALSE = @CHECK_WITH_FASTCGI_FALSE@ +CHECK_WITH_FASTCGI_TRUE = @CHECK_WITH_FASTCGI_TRUE@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CROSS_COMPILING_FALSE = @CROSS_COMPILING_FALSE@ +CROSS_COMPILING_TRUE = @CROSS_COMPILING_TRUE@ +CRYPT_LIB = @CRYPT_LIB@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ -DLIBRARY_DIR="\"$(libdir)\"" +DEPDIR = @DEPDIR@ +DL_LIB = @DL_LIB@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FAM_CFLAGS = @FAM_CFLAGS@ +FAM_LIBS = @FAM_LIBS@ +FFLAGS = @FFLAGS@ +GDBM_LIB = @GDBM_LIB@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LBER_LIB = @LBER_LIB@ +LDAP_LIB = @LDAP_LIB@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LUACONFIG = @LUACONFIG@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@ +MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@ +MAKEINFO = @MAKEINFO@ +MEMCACHE_LIB = @MEMCACHE_LIB@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_INCLUDE = @MYSQL_INCLUDE@ +MYSQL_LIBS = @MYSQL_LIBS@ +NO_RDYNAMIC_FALSE = @NO_RDYNAMIC_FALSE@ +NO_RDYNAMIC_TRUE = @NO_RDYNAMIC_TRUE@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PCRECONFIG = @PCRECONFIG@ +PCRE_LIB = @PCRE_LIB@ +PKG_CONFIG = @PKG_CONFIG@ +RANLIB = @RANLIB@ +SENDFILE_LIB = @SENDFILE_LIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SSL_LIB = @SSL_LIB@ +STRIP = @STRIP@ +U = @U@ +VERSION = @VERSION@ +XML_CFLAGS = @XML_CFLAGS@ +XML_LIBS = @XML_LIBS@ +Z_LIB = @Z_LIB@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +ac_ct_RANLIB = @ac_ct_RANLIB@ +ac_ct_STRIP = @ac_ct_STRIP@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +LEMON = $(top_builddir)/src/lemon +lemon_SOURCES = lemon.c +common_src = buffer.c log.c \ + keyvalue.c chunk.c \ + http_chunk.c stream.c fdevent.c \ + stat_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 \ + data_config.c bitset.c \ + inet_ntop_cache.c crc32.c \ + connections-glue.c \ + configfile-glue.c \ + http-header-glue.c \ + network_write.c network_linux_sendfile.c \ + network_freebsd_sendfile.c network_writev.c \ + network_solaris_sendfilev.c network_openssl.c \ + splaytree.c + +src = server.c response.c connections.c network.c configfile.c \ + configparser.c request.c proc_open.c $(am__append_2) +spawn_fcgi_SOURCES = spawn-fcgi.c + +#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 = $(am__append_1) mod_evasive.la mod_webdav.la \ + mod_cml.la mod_trigger_b4_dl.la mod_mysql_vhost.la mod_cgi.la \ + mod_scgi.la mod_staticfile.la mod_dirlisting.la \ + mod_indexfile.la mod_setenv.la mod_alias.la mod_userdir.la \ + mod_rrdtool.la mod_usertrack.la mod_proxy.la mod_ssi.la \ + mod_secdownload.la mod_expire.la mod_evhost.la \ + mod_simple_vhost.la mod_fastcgi.la mod_access.la \ + mod_compress.la mod_auth.la mod_rewrite.la mod_redirect.la \ + mod_status.la mod_accesslog.la +@NO_RDYNAMIC_TRUE@liblightcomp_la_SOURCES = $(common_src) +@NO_RDYNAMIC_TRUE@liblightcomp_la_CFLAGS = $(AM_CFLAGS) $(FAM_CFLAGS) +@NO_RDYNAMIC_TRUE@liblightcomp_la_LDFLAGS = -avoid-version -no-undefined +@NO_RDYNAMIC_TRUE@liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB) $(FAM_LIBS) +@NO_RDYNAMIC_FALSE@common_libadd = +@NO_RDYNAMIC_TRUE@common_libadd = liblightcomp.la +mod_evasive_la_SOURCES = mod_evasive.c +mod_evasive_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_evasive_la_LIBADD = $(common_libadd) +mod_webdav_la_SOURCES = mod_webdav.c +mod_webdav_la_CFLAGS = $(XML_CFLAGS) +mod_webdav_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_webdav_la_LIBADD = $(common_libadd) $(XML_LIBS) $(SQLITE_LIBS) +mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c +mod_cml_la_CFLAGS = $(AM_CFLAGS) $(LUA_CFLAGS) +mod_cml_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) $(LUA_LIBS) -lm +mod_trigger_b4_dl_la_SOURCES = mod_trigger_b4_dl.c +mod_trigger_b4_dl_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_trigger_b4_dl_la_LIBADD = $(GDBM_LIB) $(MEMCACHE_LIB) $(PCRE_LIB) $(common_libadd) +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) +mod_mysql_vhost_la_CPPFLAGS = $(MYSQL_INCLUDE) +mod_cgi_la_SOURCES = mod_cgi.c +mod_cgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_cgi_la_LIBADD = $(common_libadd) +mod_scgi_la_SOURCES = mod_scgi.c +mod_scgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_scgi_la_LIBADD = $(common_libadd) +mod_staticfile_la_SOURCES = mod_staticfile.c +mod_staticfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_staticfile_la_LIBADD = $(common_libadd) +mod_dirlisting_la_SOURCES = mod_dirlisting.c +mod_dirlisting_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_dirlisting_la_LIBADD = $(common_libadd) $(PCRE_LIB) +mod_indexfile_la_SOURCES = mod_indexfile.c +mod_indexfile_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_indexfile_la_LIBADD = $(common_libadd) +mod_setenv_la_SOURCES = mod_setenv.c +mod_setenv_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_setenv_la_LIBADD = $(common_libadd) +mod_alias_la_SOURCES = mod_alias.c +mod_alias_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_alias_la_LIBADD = $(common_libadd) +mod_userdir_la_SOURCES = mod_userdir.c +mod_userdir_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_userdir_la_LIBADD = $(common_libadd) +mod_rrdtool_la_SOURCES = mod_rrdtool.c +mod_rrdtool_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_rrdtool_la_LIBADD = $(common_libadd) +mod_usertrack_la_SOURCES = mod_usertrack.c +mod_usertrack_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_usertrack_la_LIBADD = $(common_libadd) +mod_proxy_la_SOURCES = mod_proxy.c +mod_proxy_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_proxy_la_LIBADD = $(common_libadd) +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) +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) +mod_expire_la_SOURCES = mod_expire.c +mod_expire_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_expire_la_LIBADD = $(common_libadd) +mod_evhost_la_SOURCES = mod_evhost.c +mod_evhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_evhost_la_LIBADD = $(common_libadd) +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) +mod_fastcgi_la_SOURCES = mod_fastcgi.c +mod_fastcgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_fastcgi_la_LIBADD = $(common_libadd) +mod_access_la_SOURCES = mod_access.c +mod_access_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_access_la_LIBADD = $(common_libadd) +mod_compress_la_SOURCES = mod_compress.c +mod_compress_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_compress_la_LIBADD = $(Z_LIB) $(BZ_LIB) $(common_libadd) +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) +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) +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) +mod_status_la_SOURCES = mod_status.c +mod_status_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_status_la_LIBADD = $(common_libadd) +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 http_auth.h stream.h \ + fdevent.h connections.h base.h stat_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 mod_cml.h mod_cml_funcs.h \ + splaytree.h proc_open.h + +lighttpd_SOURCES = $(src) +lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB) $(FAM_LIBS) +lighttpd_LDFLAGS = -export-dynamic +lighttpd_CCPFLAGS = $(FAM_CFLAGS) +proc_open_SOURCES = proc_open.c buffer.c +proc_open_CPPFLAGS = -DDEBUG_PROC_OPEN + +#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 +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)" + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + if test -f $$p; then \ + f=$(am__strip_dir) \ + echo " $(LIBTOOL) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \ + else :; fi; \ + done + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @set -x; list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + p=$(am__strip_dir) \ + echo " $(LIBTOOL) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \ + $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +liblightcomp.la: $(liblightcomp_la_OBJECTS) $(liblightcomp_la_DEPENDENCIES) + $(LINK) $(am_liblightcomp_la_rpath) $(liblightcomp_la_LDFLAGS) $(liblightcomp_la_OBJECTS) $(liblightcomp_la_LIBADD) $(LIBS) +mod_access.la: $(mod_access_la_OBJECTS) $(mod_access_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_access_la_LDFLAGS) $(mod_access_la_OBJECTS) $(mod_access_la_LIBADD) $(LIBS) +mod_accesslog.la: $(mod_accesslog_la_OBJECTS) $(mod_accesslog_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_accesslog_la_LDFLAGS) $(mod_accesslog_la_OBJECTS) $(mod_accesslog_la_LIBADD) $(LIBS) +mod_alias.la: $(mod_alias_la_OBJECTS) $(mod_alias_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_alias_la_LDFLAGS) $(mod_alias_la_OBJECTS) $(mod_alias_la_LIBADD) $(LIBS) +mod_auth.la: $(mod_auth_la_OBJECTS) $(mod_auth_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_auth_la_LDFLAGS) $(mod_auth_la_OBJECTS) $(mod_auth_la_LIBADD) $(LIBS) +mod_cgi.la: $(mod_cgi_la_OBJECTS) $(mod_cgi_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_cgi_la_LDFLAGS) $(mod_cgi_la_OBJECTS) $(mod_cgi_la_LIBADD) $(LIBS) +mod_cml.la: $(mod_cml_la_OBJECTS) $(mod_cml_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_cml_la_LDFLAGS) $(mod_cml_la_OBJECTS) $(mod_cml_la_LIBADD) $(LIBS) +mod_compress.la: $(mod_compress_la_OBJECTS) $(mod_compress_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_compress_la_LDFLAGS) $(mod_compress_la_OBJECTS) $(mod_compress_la_LIBADD) $(LIBS) +mod_dirlisting.la: $(mod_dirlisting_la_OBJECTS) $(mod_dirlisting_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_dirlisting_la_LDFLAGS) $(mod_dirlisting_la_OBJECTS) $(mod_dirlisting_la_LIBADD) $(LIBS) +mod_evasive.la: $(mod_evasive_la_OBJECTS) $(mod_evasive_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_evasive_la_LDFLAGS) $(mod_evasive_la_OBJECTS) $(mod_evasive_la_LIBADD) $(LIBS) +mod_evhost.la: $(mod_evhost_la_OBJECTS) $(mod_evhost_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_evhost_la_LDFLAGS) $(mod_evhost_la_OBJECTS) $(mod_evhost_la_LIBADD) $(LIBS) +mod_expire.la: $(mod_expire_la_OBJECTS) $(mod_expire_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_expire_la_LDFLAGS) $(mod_expire_la_OBJECTS) $(mod_expire_la_LIBADD) $(LIBS) +mod_fastcgi.la: $(mod_fastcgi_la_OBJECTS) $(mod_fastcgi_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_fastcgi_la_LDFLAGS) $(mod_fastcgi_la_OBJECTS) $(mod_fastcgi_la_LIBADD) $(LIBS) +mod_indexfile.la: $(mod_indexfile_la_OBJECTS) $(mod_indexfile_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_indexfile_la_LDFLAGS) $(mod_indexfile_la_OBJECTS) $(mod_indexfile_la_LIBADD) $(LIBS) +mod_mysql_vhost.la: $(mod_mysql_vhost_la_OBJECTS) $(mod_mysql_vhost_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_mysql_vhost_la_LDFLAGS) $(mod_mysql_vhost_la_OBJECTS) $(mod_mysql_vhost_la_LIBADD) $(LIBS) +mod_proxy.la: $(mod_proxy_la_OBJECTS) $(mod_proxy_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_proxy_la_LDFLAGS) $(mod_proxy_la_OBJECTS) $(mod_proxy_la_LIBADD) $(LIBS) +mod_redirect.la: $(mod_redirect_la_OBJECTS) $(mod_redirect_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_redirect_la_LDFLAGS) $(mod_redirect_la_OBJECTS) $(mod_redirect_la_LIBADD) $(LIBS) +mod_rewrite.la: $(mod_rewrite_la_OBJECTS) $(mod_rewrite_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_rewrite_la_LDFLAGS) $(mod_rewrite_la_OBJECTS) $(mod_rewrite_la_LIBADD) $(LIBS) +mod_rrdtool.la: $(mod_rrdtool_la_OBJECTS) $(mod_rrdtool_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_rrdtool_la_LDFLAGS) $(mod_rrdtool_la_OBJECTS) $(mod_rrdtool_la_LIBADD) $(LIBS) +mod_scgi.la: $(mod_scgi_la_OBJECTS) $(mod_scgi_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_scgi_la_LDFLAGS) $(mod_scgi_la_OBJECTS) $(mod_scgi_la_LIBADD) $(LIBS) +mod_secdownload.la: $(mod_secdownload_la_OBJECTS) $(mod_secdownload_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_secdownload_la_LDFLAGS) $(mod_secdownload_la_OBJECTS) $(mod_secdownload_la_LIBADD) $(LIBS) +mod_setenv.la: $(mod_setenv_la_OBJECTS) $(mod_setenv_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_setenv_la_LDFLAGS) $(mod_setenv_la_OBJECTS) $(mod_setenv_la_LIBADD) $(LIBS) +mod_simple_vhost.la: $(mod_simple_vhost_la_OBJECTS) $(mod_simple_vhost_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_simple_vhost_la_LDFLAGS) $(mod_simple_vhost_la_OBJECTS) $(mod_simple_vhost_la_LIBADD) $(LIBS) +mod_ssi.la: $(mod_ssi_la_OBJECTS) $(mod_ssi_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_ssi_la_LDFLAGS) $(mod_ssi_la_OBJECTS) $(mod_ssi_la_LIBADD) $(LIBS) +mod_staticfile.la: $(mod_staticfile_la_OBJECTS) $(mod_staticfile_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_staticfile_la_LDFLAGS) $(mod_staticfile_la_OBJECTS) $(mod_staticfile_la_LIBADD) $(LIBS) +mod_status.la: $(mod_status_la_OBJECTS) $(mod_status_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_status_la_LDFLAGS) $(mod_status_la_OBJECTS) $(mod_status_la_LIBADD) $(LIBS) +mod_trigger_b4_dl.la: $(mod_trigger_b4_dl_la_OBJECTS) $(mod_trigger_b4_dl_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_trigger_b4_dl_la_LDFLAGS) $(mod_trigger_b4_dl_la_OBJECTS) $(mod_trigger_b4_dl_la_LIBADD) $(LIBS) +mod_userdir.la: $(mod_userdir_la_OBJECTS) $(mod_userdir_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_userdir_la_LDFLAGS) $(mod_userdir_la_OBJECTS) $(mod_userdir_la_LIBADD) $(LIBS) +mod_usertrack.la: $(mod_usertrack_la_OBJECTS) $(mod_usertrack_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_usertrack_la_LDFLAGS) $(mod_usertrack_la_OBJECTS) $(mod_usertrack_la_LIBADD) $(LIBS) +mod_webdav.la: $(mod_webdav_la_OBJECTS) $(mod_webdav_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(mod_webdav_la_LDFLAGS) $(mod_webdav_la_OBJECTS) $(mod_webdav_la_LIBADD) $(LIBS) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + || test -f $$p1 \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(sbindir)" || $(mkdir_p) "$(DESTDIR)$(sbindir)" + @list='$(sbin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + || test -f $$p1 \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(sbinPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(sbindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(sbinPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(sbindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(sbindir)/$$f'"; \ + rm -f "$(DESTDIR)$(sbindir)/$$f"; \ + done + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +lemon$(EXEEXT): $(lemon_OBJECTS) $(lemon_DEPENDENCIES) + @rm -f lemon$(EXEEXT) + $(LINK) $(lemon_LDFLAGS) $(lemon_OBJECTS) $(lemon_LDADD) $(LIBS) +lighttpd$(EXEEXT): $(lighttpd_OBJECTS) $(lighttpd_DEPENDENCIES) + @rm -f lighttpd$(EXEEXT) + $(LINK) $(lighttpd_LDFLAGS) $(lighttpd_OBJECTS) $(lighttpd_LDADD) $(LIBS) +proc_open$(EXEEXT): $(proc_open_OBJECTS) $(proc_open_DEPENDENCIES) + @rm -f proc_open$(EXEEXT) + $(LINK) $(proc_open_LDFLAGS) $(proc_open_OBJECTS) $(proc_open_LDADD) $(LIBS) +spawn-fcgi$(EXEEXT): $(spawn_fcgi_OBJECTS) $(spawn_fcgi_DEPENDENCIES) + @rm -f spawn-fcgi$(EXEEXT) + $(LINK) $(spawn_fcgi_LDFLAGS) $(spawn_fcgi_OBJECTS) $(spawn_fcgi_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/array.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bitset.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chunk.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configfile-glue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configfile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/configparser.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connections-glue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connections.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc32.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_array.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_count.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_fastcgi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_integer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data_string.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/etag.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_freebsd_kqueue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_linux_rtsig.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_linux_sysepoll.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_poll.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_select.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fdevent_solaris_devpoll.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http-header-glue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_auth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_auth_digest.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http_chunk.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inet_ntop_cache.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/joblist.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyvalue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lemon.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-array.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-bitset.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-buffer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-chunk.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-configfile-glue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-connections-glue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-crc32.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_array.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_config.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_count.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_fastcgi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_integer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-data_string.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-etag.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_poll.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_select.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-http-header-glue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-http_chunk.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-joblist.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-keyvalue.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-log.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-md5.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_openssl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_write.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-network_writev.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-plugin.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-splaytree.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-stat_cache.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightcomp_la-stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_access.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_accesslog.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_alias.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_auth.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cgi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cml_la-mod_cml.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cml_la-mod_cml_funcs.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_cml_la-mod_cml_lua.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_compress.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_dirlisting.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_evasive.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_evhost.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_expire.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_fastcgi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_indexfile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_proxy.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_redirect.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_rewrite.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_rrdtool.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_scgi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_secure_download.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_setenv.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_simple_vhost.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_ssi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_ssi_expr.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_ssi_exprparser.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_staticfile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_status.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_trigger_b4_dl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_userdir.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_usertrack.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod_webdav_la-mod_webdav.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_freebsd_sendfile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_linux_sendfile.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_openssl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_solaris_sendfilev.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_write.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network_writev.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_open-buffer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_open-proc_open.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proc_open.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/request.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spawn-fcgi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/splaytree.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stat_cache.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stream.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ if $(LTCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +liblightcomp_la-buffer.lo: buffer.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-buffer.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-buffer.Tpo" -c -o liblightcomp_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-buffer.Tpo" "$(DEPDIR)/liblightcomp_la-buffer.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-buffer.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='buffer.c' object='liblightcomp_la-buffer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c + +liblightcomp_la-log.lo: log.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-log.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-log.Tpo" -c -o liblightcomp_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-log.Tpo" "$(DEPDIR)/liblightcomp_la-log.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-log.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='log.c' object='liblightcomp_la-log.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c + +liblightcomp_la-keyvalue.lo: keyvalue.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-keyvalue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-keyvalue.Tpo" -c -o liblightcomp_la-keyvalue.lo `test -f 'keyvalue.c' || echo '$(srcdir)/'`keyvalue.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-keyvalue.Tpo" "$(DEPDIR)/liblightcomp_la-keyvalue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-keyvalue.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='keyvalue.c' object='liblightcomp_la-keyvalue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-keyvalue.lo `test -f 'keyvalue.c' || echo '$(srcdir)/'`keyvalue.c + +liblightcomp_la-chunk.lo: chunk.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-chunk.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-chunk.Tpo" -c -o liblightcomp_la-chunk.lo `test -f 'chunk.c' || echo '$(srcdir)/'`chunk.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-chunk.Tpo" "$(DEPDIR)/liblightcomp_la-chunk.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-chunk.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='chunk.c' object='liblightcomp_la-chunk.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-chunk.lo `test -f 'chunk.c' || echo '$(srcdir)/'`chunk.c + +liblightcomp_la-http_chunk.lo: http_chunk.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-http_chunk.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-http_chunk.Tpo" -c -o liblightcomp_la-http_chunk.lo `test -f 'http_chunk.c' || echo '$(srcdir)/'`http_chunk.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-http_chunk.Tpo" "$(DEPDIR)/liblightcomp_la-http_chunk.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-http_chunk.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='http_chunk.c' object='liblightcomp_la-http_chunk.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-http_chunk.lo `test -f 'http_chunk.c' || echo '$(srcdir)/'`http_chunk.c + +liblightcomp_la-stream.lo: stream.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-stream.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-stream.Tpo" -c -o liblightcomp_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-stream.Tpo" "$(DEPDIR)/liblightcomp_la-stream.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-stream.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='stream.c' object='liblightcomp_la-stream.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c + +liblightcomp_la-fdevent.lo: fdevent.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent.Tpo" -c -o liblightcomp_la-fdevent.lo `test -f 'fdevent.c' || echo '$(srcdir)/'`fdevent.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent.c' object='liblightcomp_la-fdevent.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent.lo `test -f 'fdevent.c' || echo '$(srcdir)/'`fdevent.c + +liblightcomp_la-stat_cache.lo: stat_cache.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-stat_cache.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-stat_cache.Tpo" -c -o liblightcomp_la-stat_cache.lo `test -f 'stat_cache.c' || echo '$(srcdir)/'`stat_cache.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-stat_cache.Tpo" "$(DEPDIR)/liblightcomp_la-stat_cache.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-stat_cache.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='stat_cache.c' object='liblightcomp_la-stat_cache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-stat_cache.lo `test -f 'stat_cache.c' || echo '$(srcdir)/'`stat_cache.c + +liblightcomp_la-plugin.lo: plugin.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-plugin.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-plugin.Tpo" -c -o liblightcomp_la-plugin.lo `test -f 'plugin.c' || echo '$(srcdir)/'`plugin.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-plugin.Tpo" "$(DEPDIR)/liblightcomp_la-plugin.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-plugin.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='plugin.c' object='liblightcomp_la-plugin.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-plugin.lo `test -f 'plugin.c' || echo '$(srcdir)/'`plugin.c + +liblightcomp_la-joblist.lo: joblist.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-joblist.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-joblist.Tpo" -c -o liblightcomp_la-joblist.lo `test -f 'joblist.c' || echo '$(srcdir)/'`joblist.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-joblist.Tpo" "$(DEPDIR)/liblightcomp_la-joblist.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-joblist.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='joblist.c' object='liblightcomp_la-joblist.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-joblist.lo `test -f 'joblist.c' || echo '$(srcdir)/'`joblist.c + +liblightcomp_la-etag.lo: etag.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-etag.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-etag.Tpo" -c -o liblightcomp_la-etag.lo `test -f 'etag.c' || echo '$(srcdir)/'`etag.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-etag.Tpo" "$(DEPDIR)/liblightcomp_la-etag.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-etag.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='etag.c' object='liblightcomp_la-etag.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-etag.lo `test -f 'etag.c' || echo '$(srcdir)/'`etag.c + +liblightcomp_la-array.lo: array.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-array.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-array.Tpo" -c -o liblightcomp_la-array.lo `test -f 'array.c' || echo '$(srcdir)/'`array.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-array.Tpo" "$(DEPDIR)/liblightcomp_la-array.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-array.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='array.c' object='liblightcomp_la-array.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-array.lo `test -f 'array.c' || echo '$(srcdir)/'`array.c + +liblightcomp_la-data_string.lo: data_string.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_string.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_string.Tpo" -c -o liblightcomp_la-data_string.lo `test -f 'data_string.c' || echo '$(srcdir)/'`data_string.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_string.Tpo" "$(DEPDIR)/liblightcomp_la-data_string.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_string.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_string.c' object='liblightcomp_la-data_string.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_string.lo `test -f 'data_string.c' || echo '$(srcdir)/'`data_string.c + +liblightcomp_la-data_count.lo: data_count.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_count.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_count.Tpo" -c -o liblightcomp_la-data_count.lo `test -f 'data_count.c' || echo '$(srcdir)/'`data_count.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_count.Tpo" "$(DEPDIR)/liblightcomp_la-data_count.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_count.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_count.c' object='liblightcomp_la-data_count.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_count.lo `test -f 'data_count.c' || echo '$(srcdir)/'`data_count.c + +liblightcomp_la-data_array.lo: data_array.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_array.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_array.Tpo" -c -o liblightcomp_la-data_array.lo `test -f 'data_array.c' || echo '$(srcdir)/'`data_array.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_array.Tpo" "$(DEPDIR)/liblightcomp_la-data_array.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_array.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_array.c' object='liblightcomp_la-data_array.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_array.lo `test -f 'data_array.c' || echo '$(srcdir)/'`data_array.c + +liblightcomp_la-data_integer.lo: data_integer.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_integer.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_integer.Tpo" -c -o liblightcomp_la-data_integer.lo `test -f 'data_integer.c' || echo '$(srcdir)/'`data_integer.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_integer.Tpo" "$(DEPDIR)/liblightcomp_la-data_integer.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_integer.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_integer.c' object='liblightcomp_la-data_integer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_integer.lo `test -f 'data_integer.c' || echo '$(srcdir)/'`data_integer.c + +liblightcomp_la-md5.lo: md5.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-md5.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-md5.Tpo" -c -o liblightcomp_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-md5.Tpo" "$(DEPDIR)/liblightcomp_la-md5.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-md5.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5.c' object='liblightcomp_la-md5.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c + +liblightcomp_la-data_fastcgi.lo: data_fastcgi.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_fastcgi.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_fastcgi.Tpo" -c -o liblightcomp_la-data_fastcgi.lo `test -f 'data_fastcgi.c' || echo '$(srcdir)/'`data_fastcgi.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_fastcgi.Tpo" "$(DEPDIR)/liblightcomp_la-data_fastcgi.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_fastcgi.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_fastcgi.c' object='liblightcomp_la-data_fastcgi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_fastcgi.lo `test -f 'data_fastcgi.c' || echo '$(srcdir)/'`data_fastcgi.c + +liblightcomp_la-fdevent_select.lo: fdevent_select.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_select.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_select.Tpo" -c -o liblightcomp_la-fdevent_select.lo `test -f 'fdevent_select.c' || echo '$(srcdir)/'`fdevent_select.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_select.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_select.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_select.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_select.c' object='liblightcomp_la-fdevent_select.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_select.lo `test -f 'fdevent_select.c' || echo '$(srcdir)/'`fdevent_select.c + +liblightcomp_la-fdevent_linux_rtsig.lo: fdevent_linux_rtsig.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_linux_rtsig.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Tpo" -c -o liblightcomp_la-fdevent_linux_rtsig.lo `test -f 'fdevent_linux_rtsig.c' || echo '$(srcdir)/'`fdevent_linux_rtsig.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_rtsig.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_linux_rtsig.c' object='liblightcomp_la-fdevent_linux_rtsig.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_linux_rtsig.lo `test -f 'fdevent_linux_rtsig.c' || echo '$(srcdir)/'`fdevent_linux_rtsig.c + +liblightcomp_la-fdevent_poll.lo: fdevent_poll.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_poll.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_poll.Tpo" -c -o liblightcomp_la-fdevent_poll.lo `test -f 'fdevent_poll.c' || echo '$(srcdir)/'`fdevent_poll.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_poll.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_poll.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_poll.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_poll.c' object='liblightcomp_la-fdevent_poll.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_poll.lo `test -f 'fdevent_poll.c' || echo '$(srcdir)/'`fdevent_poll.c + +liblightcomp_la-fdevent_linux_sysepoll.lo: fdevent_linux_sysepoll.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_linux_sysepoll.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Tpo" -c -o liblightcomp_la-fdevent_linux_sysepoll.lo `test -f 'fdevent_linux_sysepoll.c' || echo '$(srcdir)/'`fdevent_linux_sysepoll.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_linux_sysepoll.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_linux_sysepoll.c' object='liblightcomp_la-fdevent_linux_sysepoll.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_linux_sysepoll.lo `test -f 'fdevent_linux_sysepoll.c' || echo '$(srcdir)/'`fdevent_linux_sysepoll.c + +liblightcomp_la-fdevent_solaris_devpoll.lo: fdevent_solaris_devpoll.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_solaris_devpoll.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Tpo" -c -o liblightcomp_la-fdevent_solaris_devpoll.lo `test -f 'fdevent_solaris_devpoll.c' || echo '$(srcdir)/'`fdevent_solaris_devpoll.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_solaris_devpoll.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_solaris_devpoll.c' object='liblightcomp_la-fdevent_solaris_devpoll.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_solaris_devpoll.lo `test -f 'fdevent_solaris_devpoll.c' || echo '$(srcdir)/'`fdevent_solaris_devpoll.c + +liblightcomp_la-fdevent_freebsd_kqueue.lo: fdevent_freebsd_kqueue.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-fdevent_freebsd_kqueue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Tpo" -c -o liblightcomp_la-fdevent_freebsd_kqueue.lo `test -f 'fdevent_freebsd_kqueue.c' || echo '$(srcdir)/'`fdevent_freebsd_kqueue.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Tpo" "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-fdevent_freebsd_kqueue.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='fdevent_freebsd_kqueue.c' object='liblightcomp_la-fdevent_freebsd_kqueue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-fdevent_freebsd_kqueue.lo `test -f 'fdevent_freebsd_kqueue.c' || echo '$(srcdir)/'`fdevent_freebsd_kqueue.c + +liblightcomp_la-data_config.lo: data_config.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-data_config.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-data_config.Tpo" -c -o liblightcomp_la-data_config.lo `test -f 'data_config.c' || echo '$(srcdir)/'`data_config.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-data_config.Tpo" "$(DEPDIR)/liblightcomp_la-data_config.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-data_config.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='data_config.c' object='liblightcomp_la-data_config.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-data_config.lo `test -f 'data_config.c' || echo '$(srcdir)/'`data_config.c + +liblightcomp_la-bitset.lo: bitset.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-bitset.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-bitset.Tpo" -c -o liblightcomp_la-bitset.lo `test -f 'bitset.c' || echo '$(srcdir)/'`bitset.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-bitset.Tpo" "$(DEPDIR)/liblightcomp_la-bitset.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-bitset.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='bitset.c' object='liblightcomp_la-bitset.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-bitset.lo `test -f 'bitset.c' || echo '$(srcdir)/'`bitset.c + +liblightcomp_la-inet_ntop_cache.lo: inet_ntop_cache.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-inet_ntop_cache.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Tpo" -c -o liblightcomp_la-inet_ntop_cache.lo `test -f 'inet_ntop_cache.c' || echo '$(srcdir)/'`inet_ntop_cache.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Tpo" "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-inet_ntop_cache.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='inet_ntop_cache.c' object='liblightcomp_la-inet_ntop_cache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-inet_ntop_cache.lo `test -f 'inet_ntop_cache.c' || echo '$(srcdir)/'`inet_ntop_cache.c + +liblightcomp_la-crc32.lo: crc32.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-crc32.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-crc32.Tpo" -c -o liblightcomp_la-crc32.lo `test -f 'crc32.c' || echo '$(srcdir)/'`crc32.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-crc32.Tpo" "$(DEPDIR)/liblightcomp_la-crc32.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-crc32.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='crc32.c' object='liblightcomp_la-crc32.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-crc32.lo `test -f 'crc32.c' || echo '$(srcdir)/'`crc32.c + +liblightcomp_la-connections-glue.lo: connections-glue.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-connections-glue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-connections-glue.Tpo" -c -o liblightcomp_la-connections-glue.lo `test -f 'connections-glue.c' || echo '$(srcdir)/'`connections-glue.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-connections-glue.Tpo" "$(DEPDIR)/liblightcomp_la-connections-glue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-connections-glue.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='connections-glue.c' object='liblightcomp_la-connections-glue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-connections-glue.lo `test -f 'connections-glue.c' || echo '$(srcdir)/'`connections-glue.c + +liblightcomp_la-configfile-glue.lo: configfile-glue.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-configfile-glue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-configfile-glue.Tpo" -c -o liblightcomp_la-configfile-glue.lo `test -f 'configfile-glue.c' || echo '$(srcdir)/'`configfile-glue.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-configfile-glue.Tpo" "$(DEPDIR)/liblightcomp_la-configfile-glue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-configfile-glue.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='configfile-glue.c' object='liblightcomp_la-configfile-glue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-configfile-glue.lo `test -f 'configfile-glue.c' || echo '$(srcdir)/'`configfile-glue.c + +liblightcomp_la-http-header-glue.lo: http-header-glue.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-http-header-glue.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-http-header-glue.Tpo" -c -o liblightcomp_la-http-header-glue.lo `test -f 'http-header-glue.c' || echo '$(srcdir)/'`http-header-glue.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-http-header-glue.Tpo" "$(DEPDIR)/liblightcomp_la-http-header-glue.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-http-header-glue.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='http-header-glue.c' object='liblightcomp_la-http-header-glue.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-http-header-glue.lo `test -f 'http-header-glue.c' || echo '$(srcdir)/'`http-header-glue.c + +liblightcomp_la-network_write.lo: network_write.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_write.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_write.Tpo" -c -o liblightcomp_la-network_write.lo `test -f 'network_write.c' || echo '$(srcdir)/'`network_write.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_write.Tpo" "$(DEPDIR)/liblightcomp_la-network_write.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_write.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_write.c' object='liblightcomp_la-network_write.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_write.lo `test -f 'network_write.c' || echo '$(srcdir)/'`network_write.c + +liblightcomp_la-network_linux_sendfile.lo: network_linux_sendfile.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_linux_sendfile.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Tpo" -c -o liblightcomp_la-network_linux_sendfile.lo `test -f 'network_linux_sendfile.c' || echo '$(srcdir)/'`network_linux_sendfile.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Tpo" "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_linux_sendfile.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_linux_sendfile.c' object='liblightcomp_la-network_linux_sendfile.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_linux_sendfile.lo `test -f 'network_linux_sendfile.c' || echo '$(srcdir)/'`network_linux_sendfile.c + +liblightcomp_la-network_freebsd_sendfile.lo: network_freebsd_sendfile.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_freebsd_sendfile.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Tpo" -c -o liblightcomp_la-network_freebsd_sendfile.lo `test -f 'network_freebsd_sendfile.c' || echo '$(srcdir)/'`network_freebsd_sendfile.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Tpo" "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_freebsd_sendfile.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_freebsd_sendfile.c' object='liblightcomp_la-network_freebsd_sendfile.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_freebsd_sendfile.lo `test -f 'network_freebsd_sendfile.c' || echo '$(srcdir)/'`network_freebsd_sendfile.c + +liblightcomp_la-network_writev.lo: network_writev.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_writev.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_writev.Tpo" -c -o liblightcomp_la-network_writev.lo `test -f 'network_writev.c' || echo '$(srcdir)/'`network_writev.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_writev.Tpo" "$(DEPDIR)/liblightcomp_la-network_writev.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_writev.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_writev.c' object='liblightcomp_la-network_writev.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_writev.lo `test -f 'network_writev.c' || echo '$(srcdir)/'`network_writev.c + +liblightcomp_la-network_solaris_sendfilev.lo: network_solaris_sendfilev.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_solaris_sendfilev.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Tpo" -c -o liblightcomp_la-network_solaris_sendfilev.lo `test -f 'network_solaris_sendfilev.c' || echo '$(srcdir)/'`network_solaris_sendfilev.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Tpo" "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_solaris_sendfilev.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_solaris_sendfilev.c' object='liblightcomp_la-network_solaris_sendfilev.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_solaris_sendfilev.lo `test -f 'network_solaris_sendfilev.c' || echo '$(srcdir)/'`network_solaris_sendfilev.c + +liblightcomp_la-network_openssl.lo: network_openssl.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-network_openssl.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-network_openssl.Tpo" -c -o liblightcomp_la-network_openssl.lo `test -f 'network_openssl.c' || echo '$(srcdir)/'`network_openssl.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-network_openssl.Tpo" "$(DEPDIR)/liblightcomp_la-network_openssl.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-network_openssl.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='network_openssl.c' object='liblightcomp_la-network_openssl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-network_openssl.lo `test -f 'network_openssl.c' || echo '$(srcdir)/'`network_openssl.c + +liblightcomp_la-splaytree.lo: splaytree.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -MT liblightcomp_la-splaytree.lo -MD -MP -MF "$(DEPDIR)/liblightcomp_la-splaytree.Tpo" -c -o liblightcomp_la-splaytree.lo `test -f 'splaytree.c' || echo '$(srcdir)/'`splaytree.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/liblightcomp_la-splaytree.Tpo" "$(DEPDIR)/liblightcomp_la-splaytree.Plo"; else rm -f "$(DEPDIR)/liblightcomp_la-splaytree.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='splaytree.c' object='liblightcomp_la-splaytree.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(liblightcomp_la_CFLAGS) $(CFLAGS) -c -o liblightcomp_la-splaytree.lo `test -f 'splaytree.c' || echo '$(srcdir)/'`splaytree.c + +mod_cml_la-mod_cml.lo: mod_cml.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -MT mod_cml_la-mod_cml.lo -MD -MP -MF "$(DEPDIR)/mod_cml_la-mod_cml.Tpo" -c -o mod_cml_la-mod_cml.lo `test -f 'mod_cml.c' || echo '$(srcdir)/'`mod_cml.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_cml_la-mod_cml.Tpo" "$(DEPDIR)/mod_cml_la-mod_cml.Plo"; else rm -f "$(DEPDIR)/mod_cml_la-mod_cml.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_cml.c' object='mod_cml_la-mod_cml.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -c -o mod_cml_la-mod_cml.lo `test -f 'mod_cml.c' || echo '$(srcdir)/'`mod_cml.c + +mod_cml_la-mod_cml_lua.lo: mod_cml_lua.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -MT mod_cml_la-mod_cml_lua.lo -MD -MP -MF "$(DEPDIR)/mod_cml_la-mod_cml_lua.Tpo" -c -o mod_cml_la-mod_cml_lua.lo `test -f 'mod_cml_lua.c' || echo '$(srcdir)/'`mod_cml_lua.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_cml_la-mod_cml_lua.Tpo" "$(DEPDIR)/mod_cml_la-mod_cml_lua.Plo"; else rm -f "$(DEPDIR)/mod_cml_la-mod_cml_lua.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_cml_lua.c' object='mod_cml_la-mod_cml_lua.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -c -o mod_cml_la-mod_cml_lua.lo `test -f 'mod_cml_lua.c' || echo '$(srcdir)/'`mod_cml_lua.c + +mod_cml_la-mod_cml_funcs.lo: mod_cml_funcs.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -MT mod_cml_la-mod_cml_funcs.lo -MD -MP -MF "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Tpo" -c -o mod_cml_la-mod_cml_funcs.lo `test -f 'mod_cml_funcs.c' || echo '$(srcdir)/'`mod_cml_funcs.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Tpo" "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Plo"; else rm -f "$(DEPDIR)/mod_cml_la-mod_cml_funcs.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_cml_funcs.c' object='mod_cml_la-mod_cml_funcs.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_cml_la_CFLAGS) $(CFLAGS) -c -o mod_cml_la-mod_cml_funcs.lo `test -f 'mod_cml_funcs.c' || echo '$(srcdir)/'`mod_cml_funcs.c + +mod_mysql_vhost_la-mod_mysql_vhost.lo: mod_mysql_vhost.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(mod_mysql_vhost_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT mod_mysql_vhost_la-mod_mysql_vhost.lo -MD -MP -MF "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Tpo" -c -o mod_mysql_vhost_la-mod_mysql_vhost.lo `test -f 'mod_mysql_vhost.c' || echo '$(srcdir)/'`mod_mysql_vhost.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Tpo" "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Plo"; else rm -f "$(DEPDIR)/mod_mysql_vhost_la-mod_mysql_vhost.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_mysql_vhost.c' object='mod_mysql_vhost_la-mod_mysql_vhost.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(mod_mysql_vhost_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o mod_mysql_vhost_la-mod_mysql_vhost.lo `test -f 'mod_mysql_vhost.c' || echo '$(srcdir)/'`mod_mysql_vhost.c + +mod_webdav_la-mod_webdav.lo: mod_webdav.c +@am__fastdepCC_TRUE@ if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_webdav_la_CFLAGS) $(CFLAGS) -MT mod_webdav_la-mod_webdav.lo -MD -MP -MF "$(DEPDIR)/mod_webdav_la-mod_webdav.Tpo" -c -o mod_webdav_la-mod_webdav.lo `test -f 'mod_webdav.c' || echo '$(srcdir)/'`mod_webdav.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/mod_webdav_la-mod_webdav.Tpo" "$(DEPDIR)/mod_webdav_la-mod_webdav.Plo"; else rm -f "$(DEPDIR)/mod_webdav_la-mod_webdav.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='mod_webdav.c' object='mod_webdav_la-mod_webdav.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mod_webdav_la_CFLAGS) $(CFLAGS) -c -o mod_webdav_la-mod_webdav.lo `test -f 'mod_webdav.c' || echo '$(srcdir)/'`mod_webdav.c + +proc_open-proc_open.o: proc_open.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-proc_open.o -MD -MP -MF "$(DEPDIR)/proc_open-proc_open.Tpo" -c -o proc_open-proc_open.o `test -f 'proc_open.c' || echo '$(srcdir)/'`proc_open.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-proc_open.Tpo" "$(DEPDIR)/proc_open-proc_open.Po"; else rm -f "$(DEPDIR)/proc_open-proc_open.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='proc_open.c' object='proc_open-proc_open.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-proc_open.o `test -f 'proc_open.c' || echo '$(srcdir)/'`proc_open.c + +proc_open-proc_open.obj: proc_open.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-proc_open.obj -MD -MP -MF "$(DEPDIR)/proc_open-proc_open.Tpo" -c -o proc_open-proc_open.obj `if test -f 'proc_open.c'; then $(CYGPATH_W) 'proc_open.c'; else $(CYGPATH_W) '$(srcdir)/proc_open.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-proc_open.Tpo" "$(DEPDIR)/proc_open-proc_open.Po"; else rm -f "$(DEPDIR)/proc_open-proc_open.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='proc_open.c' object='proc_open-proc_open.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-proc_open.obj `if test -f 'proc_open.c'; then $(CYGPATH_W) 'proc_open.c'; else $(CYGPATH_W) '$(srcdir)/proc_open.c'; fi` + +proc_open-buffer.o: buffer.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-buffer.o -MD -MP -MF "$(DEPDIR)/proc_open-buffer.Tpo" -c -o proc_open-buffer.o `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-buffer.Tpo" "$(DEPDIR)/proc_open-buffer.Po"; else rm -f "$(DEPDIR)/proc_open-buffer.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='buffer.c' object='proc_open-buffer.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-buffer.o `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c + +proc_open-buffer.obj: buffer.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT proc_open-buffer.obj -MD -MP -MF "$(DEPDIR)/proc_open-buffer.Tpo" -c -o proc_open-buffer.obj `if test -f 'buffer.c'; then $(CYGPATH_W) 'buffer.c'; else $(CYGPATH_W) '$(srcdir)/buffer.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/proc_open-buffer.Tpo" "$(DEPDIR)/proc_open-buffer.Po"; else rm -f "$(DEPDIR)/proc_open-buffer.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='buffer.c' object='proc_open-buffer.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(proc_open_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o proc_open-buffer.obj `if test -f 'buffer.c'; then $(CYGPATH_W) 'buffer.c'; else $(CYGPATH_W) '$(srcdir)/buffer.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(HEADERS) +install-binPROGRAMS: install-libLTLIBRARIES + +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool clean-noinstPROGRAMS clean-sbinPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-exec-am: install-binPROGRAMS install-libLTLIBRARIES \ + install-sbinPROGRAMS + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-info-am \ + uninstall-libLTLIBRARIES uninstall-sbinPROGRAMS + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic clean-libLTLIBRARIES clean-libtool \ + clean-noinstPROGRAMS clean-sbinPROGRAMS ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-binPROGRAMS install-data \ + install-data-am install-exec install-exec-am install-info \ + install-info-am install-libLTLIBRARIES install-man \ + install-sbinPROGRAMS install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS \ + uninstall-info-am uninstall-libLTLIBRARIES \ + uninstall-sbinPROGRAMS + + +#simple_fcgi_SOURCES=simple-fcgi.c +#simple_fcgi_LDADD=-lfcgi + +@CROSS_COMPILING_TRUE@configparser.c configparser.h: +@CROSS_COMPILING_TRUE@mod_ssi_exprparser.c mod_ssi_exprparser.h: +@CROSS_COMPILING_FALSE@configparser.y: lemon +@CROSS_COMPILING_FALSE@mod_ssi_exprparser.y: lemon + +@CROSS_COMPILING_FALSE@configparser.c configparser.h: configparser.y +@CROSS_COMPILING_FALSE@ rm -f configparser.h +@CROSS_COMPILING_FALSE@ $(LEMON) -q $(srcdir)/configparser.y $(srcdir)/lempar.c + +@CROSS_COMPILING_FALSE@mod_ssi_exprparser.c mod_ssi_exprparser.h: mod_ssi_exprparser.y +@CROSS_COMPILING_FALSE@ rm -f mod_ssi_exprparser.h +@CROSS_COMPILING_FALSE@ $(LEMON) -q $(srcdir)/mod_ssi_exprparser.y $(srcdir)/lempar.c + +configfile.c: configparser.h +mod_ssi_expr.c: mod_ssi_exprparser.h +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..14afa28 --- /dev/null +++ b/src/array.c @@ -0,0 +1,369 @@ +#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; +} + +array *array_init_array(array *src) { + size_t i; + array *a = array_init(); + + a->used = src->used; + a->size = src->size; + a->next_power_of_2 = src->next_power_of_2; + a->unique_ndx = src->unique_ndx; + + a->data = malloc(sizeof(*src->data) * src->size); + for (i = 0; i < src->size; i++) { + if (src->data[i]) a->data[i] = src->data[i]->copy(src->data[i]); + else a->data[i] = NULL; + } + + a->sorted = malloc(sizeof(*src->sorted) * src->size); + memcpy(a->sorted, src->sorted, sizeof(*src->sorted) * src->size); + return a; +} + +void array_free(array *a) { + size_t i; + if (!a) return; + + if (!a->is_weakref) { + 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; + + if (!a->is_weakref) { + for (i = 0; i < a->used; i++) { + a->data[i]->reset(a->data[i]); + } + } + + a->used = 0; +} + +data_unset *array_pop(array *a) { + data_unset *du; + + assert(a->used != 0); + + a->used --; + du = a->data[a->used]; + a->data[a->used] = NULL; + + return du; +} + +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; +} + +/* replace or insert data, return the old one with the same key */ +data_unset *array_replace(array *a, data_unset *du) { + int ndx; + + if (-1 == (ndx = array_get_index(a, du->key->ptr, du->key->used, NULL))) { + array_insert_unique(a, du); + return NULL; + } else { + data_unset *old = a->data[ndx]; + a->data[ndx] = du; + return old; + } +} + +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 || str->is_index_key) { + buffer_copy_long(str->key, a->unique_ndx++); + str->is_index_key = 1; + } + + /* 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; +} + +void array_print_indent(int depth) { + int i; + for (i = 0; i < depth; i ++) { + fprintf(stderr, " "); + } +} + +size_t array_get_max_key_length(array *a) { + size_t maxlen, i; + + maxlen = 0; + for (i = 0; i < a->used; i ++) { + data_unset *du = a->data[i]; + size_t len = strlen(du->key->ptr); + + if (len > maxlen) { + maxlen = len; + } + } + return maxlen; +} + +int array_print(array *a, int depth) { + size_t i; + size_t maxlen; + int oneline = 1; + + if (a->used > 5) { + oneline = 0; + } + for (i = 0; i < a->used && oneline; i++) { + data_unset *du = a->data[i]; + if (!du->is_index_key) { + oneline = 0; + break; + } + switch (du->type) { + case TYPE_INTEGER: + case TYPE_STRING: + case TYPE_COUNT: + break; + default: + oneline = 0; + break; + } + } + if (oneline) { + fprintf(stderr, "("); + for (i = 0; i < a->used; i++) { + data_unset *du = a->data[i]; + if (i != 0) { + fprintf(stderr, ", "); + } + du->print(du, depth + 1); + } + fprintf(stderr, ")"); + return 0; + } + + maxlen = array_get_max_key_length(a); + fprintf(stderr, "(\n"); + for (i = 0; i < a->used; i++) { + data_unset *du = a->data[i]; + array_print_indent(depth + 1); + if (!du->is_index_key) { + int j; + + if (i && (i % 5) == 0) { + fprintf(stderr, "# %zd\n", i); + array_print_indent(depth + 1); + } + fprintf(stderr, "\"%s\"", du->key->ptr); + for (j = maxlen - strlen(du->key->ptr); j > 0; j --) { + fprintf(stderr, " "); + } + fprintf(stderr, " => "); + } + du->print(du, depth + 1); + fprintf(stderr, ",\n"); + } + if (!(i && (i - 1 % 5) == 0)) { + array_print_indent(depth + 1); + fprintf(stderr, "# %zd\n", i); + } + array_print_indent(depth); + fprintf(stderr, ")"); + + 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, 0); + + 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 0000000..27f27a4 --- /dev/null +++ b/src/array.h @@ -0,0 +1,150 @@ +#ifndef ARRAY_H +#define ARRAY_H + +#include <stdlib.h> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#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; \ + int is_index_key; /* 1 if key is a array index (autogenerated keys) */ \ + struct data_unset *(*copy)(const struct data_unset *src); \ + 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)(const struct data_unset *p, int depth) + +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; + int is_weakref; /* data is weakref, don't bother the data */ +} 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; + +#define PATCHES NULL, "SERVERsocket", "HTTPurl", "HTTPhost", "HTTPreferer", "HTTPuseragent", "HTTPcookie", "HTTPremoteip" +typedef enum { + COMP_UNSET, + COMP_SERVER_SOCKET, COMP_HTTP_URL, COMP_HTTP_HOST, COMP_HTTP_REFERER, COMP_HTTP_USERAGENT, COMP_HTTP_COOKIE, COMP_HTTP_REMOTEIP +} comp_key_t; + +/* $HTTP["host"] == "incremental.home.kneschke.de" { ... } + * for print: comp_key op string + * for compare: comp cond string/regex + */ + +typedef struct _data_config data_config; +struct _data_config { + DATA_UNSET; + + array *value; + + buffer *comp_key; + comp_key_t comp; + + config_cond_t cond; + buffer *op; + + int context_ndx; /* more or less like an id */ + array *childs; + /* nested */ + data_config *parent; + /* for chaining only */ + data_config *prev; + data_config *next; + + buffer *string; +#ifdef HAVE_PCRE_H + pcre *regex; + pcre_extra *regex_study; +#endif +}; + +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; + + time_t disable_ts; + int is_disabled; + size_t balance; + + int usage; /* fair-balancing needs the no. of connections active on this host */ + int last_used_ndx; /* round robin */ +} data_fastcgi; + +data_fastcgi *data_fastcgi_init(void); + +array *array_init(void); +array *array_init_array(array *a); +void array_free(array *a); +void array_reset(array *a); +int array_insert_unique(array *a, data_unset *str); +data_unset *array_pop(array *a); +int array_print(array *a, int depth); +data_unset *array_get_unused_element(array *a, data_type_t t); +data_unset *array_get_element(array *a, const char *key); +data_unset *array_replace(array *a, data_unset *du); +int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len); +void array_print_indent(int depth); +size_t array_get_max_key_length(array *a); + +#endif diff --git a/src/base.h b/src/base.h new file mode 100644 index 0000000..98e23b8 --- /dev/null +++ b/src/base.h @@ -0,0 +1,606 @@ +#ifndef _BASE_H_ +#define _BASE_H_ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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" +#include "splaytree.h" + + +#if defined HAVE_LIBSSL && defined HAVE_OPENSSL_SSL_H +# define USE_OPENSSL +# include <openssl/ssl.h> +#endif + +#ifdef HAVE_FAM_H +# include <fam.h> +#endif + +#ifndef O_BINARY +# define O_BINARY 0 +#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; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un un; +#endif + 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 */ + 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 *scheme; + buffer *authority; + buffer *path; + buffer *path_raw; + buffer *query; +} request_uri; + +typedef struct { + buffer *path; + buffer *basedir; /* path = "(basedir)(.*)" */ + + buffer *doc_root; /* path = doc_root + rel_path */ + buffer *rel_path; + + buffer *etag; +} physical; + +typedef struct { + buffer *name; + buffer *etag; + + struct stat st; + + time_t stat_ts; + +#ifdef HAVE_FAM_H + int dir_version; + int dir_ndx; +#endif + + buffer *content_type; +} stat_cache_entry; + +typedef struct { + splay_tree *files; /* the nodes of the tree are stat_cache_entry's */ + + buffer *dir_name; /* for building the dirname from the filename */ +#ifdef HAVE_FAM_H + splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */ + + FAMConnection *fam; + int fam_fcce_ndx; +#endif +} stat_cache; + +typedef struct { + array *mimetypes; + + /* virtual-servers */ + buffer *document_root; + buffer *server_name; + buffer *error_handler; + buffer *server_tag; + buffer *dirlist_encoding; + buffer *errorfile_prefix; + + 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; + unsigned short range_requests; + + /* debug */ + + unsigned short log_file_not_found; + unsigned short log_request_header; + unsigned short log_request_handling; + unsigned short log_response_header; + unsigned short log_condition_handling; + + + /* server wide */ + buffer *ssl_pemfile; + buffer *ssl_ca_file; + unsigned short use_ipv6; + unsigned short is_ssl; + unsigned short allow_http11; + unsigned short force_lowercase_filenames; /* if the FS is case-insensitive, force all files to lower-case */ + 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; + +/* the order of the items should be the same as they are processed + * read before write as we use this later */ +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 enum { COND_RESULT_UNSET, COND_RESULT_FALSE, COND_RESULT_TRUE } cond_result_t; +typedef struct { + cond_result_t result; + int patterncount; + int matches[3 * 10]; + buffer *comp_value; /* just a pointer */ +} cond_cache_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 */ + size_t loops_per_request; /* to catch endless loops in a single request + * + * used by mod_rewrite, mod_fastcgi, ... and others + * this is self-protection + */ + + 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; /* a large queue for low-level write ( HTTP response ) [ file, mem ] */ + chunkqueue *read_queue; /* a small queue for low-level read ( HTTP request ) [ mem ] */ + chunkqueue *request_content_queue; /* takes request-content into tempfile if necessary [ tempfile, mem ]*/ + + 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; + buffer *dst_addr_buf; + + /* 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; + + void **plugin_ctx; /* plugin connection specific config */ + + specific_config conf; /* global connection specific config */ + cond_cache_t *cond_cache; + + 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 *errorlog_file; + unsigned short errorlog_use_syslog; + + unsigned short dont_daemonize; + buffer *changeroot; + buffer *username; + buffer *groupname; + + buffer *pid_file; + + buffer *event_handler; + + buffer *modules_dir; + buffer *network_backend; + array *modules; + array *upload_tempdirs; + + unsigned short max_worker; + unsigned short max_fds; + unsigned short max_conns; + unsigned short max_request_size; + + unsigned short log_request_header_on_error; + unsigned short log_state_handling; + + enum { STAT_CACHE_ENGINE_UNSET, + STAT_CACHE_ENGINE_NONE, + STAT_CACHE_ENGINE_SIMPLE, + STAT_CACHE_ENGINE_FAM + } stat_cache_engine; + unsigned short enable_cores; +} server_config; + +typedef struct { + sock_addr addr; + int fd; + int fde_ndx; + + buffer *ssl_pemfile; + buffer *ssl_ca_file; + unsigned short use_ipv6; + unsigned short is_ssl; + + 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 { + server_socket_array srv_sockets; + + /* the errorlog */ + int errorlog_fd; + enum { ERRORLOG_STDERR, ERRORLOG_FILE, ERRORLOG_SYSLOG } errorlog_mode; + buffer *errorlog_buf; + + fdevents *ev, *ev_ins; + + buffer_plugin plugins; + void *plugin_slots; + + /* counters */ + 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; + + size_t max_conns; + + /* buffers */ + buffer *parse_full_path; + buffer *response_header; + buffer *response_range; + buffer *tmp_buf; + + buffer *tmp_chunk_len; + + buffer *empty_string; /* is necessary for cond_match */ + + buffer *cond_check_buf; + + /* 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; + + stat_cache *stat_cache; + + /** + * The status array can carry all the status information you want + * the key to the array is <module-prefix>.<name> + * and the values are counters + * + * example: + * fastcgi.backends = 10 + * fastcgi.active-backends = 6 + * fastcgi.backend.<key>.load = 24 + * fastcgi.backend.<key>.... + * + * fastcgi.backend.<key>.disconnects = ... + */ + array *status; + + fdevent_handler_t event_handler; + + int (* network_backend_write)(struct server *srv, connection *con, int fd, chunkqueue *cq); + int (* network_backend_read)(struct server *srv, connection *con, int fd, chunkqueue *cq); +#ifdef USE_OPENSSL + int (* network_ssl_backend_write)(struct server *srv, connection *con, SSL *ssl, chunkqueue *cq); + int (* network_ssl_backend_read)(struct server *srv, connection *con, SSL *ssl, chunkqueue *cq); +#endif + + uid_t uid; + gid_t gid; +} server; + + +#endif diff --git a/src/bitset.c b/src/bitset.c new file mode 100644 index 0000000..7fe5662 --- /dev/null +++ b/src/bitset.c @@ -0,0 +1,67 @@ +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.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 0000000..467e13f --- /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 0000000..40b8cb9 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,1023 @@ +#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; +} + +buffer *buffer_init_buffer(buffer *src) { + buffer *b = buffer_init(); + buffer_copy_string_buffer(b, src); + return b; +} + +/** + * free the buffer + * + */ + +void buffer_free(buffer *b) { + if (!b) return; + + free(b->ptr); + 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' counter 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) + 1; + buffer_prepare_copy(b, s_len); + + memcpy(b->ptr, s, s_len); + b->used = s_len; + + 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 + buffer_prepare_copy(b, s_len + 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 -1; + + 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; + + s_len = strlen(s); + buffer_prepare_append(b, s_len + 1); + if (b->used == 0) + b->used++; + + 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; + + if (!s || !b) return -1; + + s_len = strlen(s); + buffer_prepare_append(b, maxlen + 1); + if (b->used == 0) + b->used++; + + memcpy(b->ptr + b->used - 1, s, s_len); + if (maxlen > s_len) { + memset(b->ptr + b->used - 1 + s_len, ' ', maxlen - s_len); + } + + b->used += maxlen; + b->ptr[b->used - 1] = '\0'; + 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; + + buffer_prepare_append(b, s_len + 1); + if (b->used == 0) + b->used++; + + memcpy(b->ptr + b->used - 1, s, s_len); + b->used += s_len; + b->ptr[b->used - 1] = '\0'; + + return 0; +} + +int buffer_append_string_buffer(buffer *b, const buffer *src) { + if (!src) return -1; + 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; + + buffer_prepare_append(b, s_len); + 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_long_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); + if (b->used == 0) + b->used++; + buf = b->ptr + (b->used - 1); + b->used += shift; + + shift <<= 2; + while (shift > 0) { + shift -= 4; + *(buf++) = hex_chars[(value >> shift) & 0x0F]; + } + *buf = '\0'; + + return 0; +} + +int ltostr(char *buf, long val) { + char swap; + char *end; + int len = 1; + + if (val < 0) { + len++; + *(buf++) = '-'; + val = -val; + } + + end = buf; + while (val > 9) { + *(end++) = '0' + (val % 10); + val = val / 10; + } + *(end) = '0' + val; + *(end + 1) = '\0'; + len += end - buf; + + while (buf < end) { + swap = *end; + *end = *buf; + *buf = swap; + + buf++; + end--; + } + + return len; +} + +int buffer_append_long(buffer *b, long val) { + if (!b) return -1; + + buffer_prepare_append(b, 32); + if (b->used == 0) + b->used++; + + b->used += ltostr(b->ptr + (b->used - 1), val); + return 0; +} + +int buffer_copy_long(buffer *b, long val) { + if (!b) return -1; + + b->used = 0; + return buffer_append_long(b, val); +} + +#if !defined(SIZEOF_LONG) || (SIZEOF_LONG != SIZEOF_OFF_T) +int buffer_append_off_t(buffer *b, off_t val) { + char swap; + char *end; + char *start; + int len = 1; + + if (!b) return -1; + + buffer_prepare_append(b, 32); + if (b->used == 0) + b->used++; + + start = b->ptr + (b->used - 1); + if (val < 0) { + len++; + *(start++) = '-'; + val = -val; + } + + end = start; + while (val > 9) { + *(end++) = '0' + (val % 10); + val = val / 10; + } + *(end) = '0' + val; + *(end + 1) = '\0'; + len += end - start; + + while (start < end) { + swap = *end; + *end = *start; + *start = swap; + + start++; + end--; + } + + b->used += len; + return 0; +} + +int buffer_copy_off_t(buffer *b, off_t val) { + if (!b) return -1; + + b->used = 0; + return buffer_append_off_t(b, val); +} +#endif /* !defined(SIZEOF_LONG) || (SIZEOF_LONG != SIZEOF_OFF_T) */ + +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; +} + +void buffer_array_reset(buffer_array *b) { + size_t i; + + if (!b) return; + + /* if they are too large, reduce them */ + for (i = 0; i < b->used; i++) { + buffer_reset(b->ptr[i]); + } + + b->used = 0; +} + + +/** + * 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 + * + * HISTORY: this function was pretty much optimized, but didn't handled + * alignment properly. + */ + +int buffer_is_equal(buffer *a, buffer *b) { + if (a->used != b->used) return 0; + if (a->used == 0) return 1; + + return (0 == strcmp(a->ptr, b->ptr)); +} + +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; +} + +const char encoded_chars_rel_uri_part[] = { + /* + 0 1 2 3 4 5 6 7 8 9 A B C D E F + */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */ + 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, /* 20 - 2F space " # $ % & ' + , / */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 30 - 3F : ; = ? @ < > */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */ +}; + +const char encoded_chars_rel_uri[] = { + /* + 0 1 2 3 4 5 6 7 8 9 A B C D E F + */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */ + 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, /* 20 - 2F space " # $ % & ' + , / */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 30 - 3F : ; = ? @ < > */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */ +}; + +const char encoded_chars_html[] = { + /* + 0 1 2 3 4 5 6 7 8 9 A B C D E F + */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */ + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 2F & */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* 30 - 3F < > */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */ +}; + +const char encoded_chars_minimal_xml[] = { + /* + 0 1 2 3 4 5 6 7 8 9 A B C D E F + */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */ + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 2F & */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* 30 - 3F < > */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 - 4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 70 - 7F DEL */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ +}; + +const char encoded_chars_hex[] = { + /* + 0 1 2 3 4 5 6 7 8 9 A B C D E F + */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 00 - 0F control chars */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 10 - 1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 30 - 3F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 70 - 7F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 80 - 8F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 - 9F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* A0 - AF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* B0 - BF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* C0 - CF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* D0 - DF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* E0 - EF */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* F0 - FF */ +}; + + +int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding) { + unsigned char *ds, *d; + size_t d_len, ndx; + const char *map = NULL; + + if (!s || !b) return -1; + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + if (s_len == 0) return 0; + + switch(encoding) { + case ENCODING_REL_URI: + map = encoded_chars_rel_uri; + break; + case ENCODING_REL_URI_PART: + map = encoded_chars_rel_uri_part; + break; + case ENCODING_HTML: + map = encoded_chars_html; + break; + case ENCODING_MINIMAL_XML: + map = encoded_chars_minimal_xml; + break; + case ENCODING_HEX: + map = encoded_chars_hex; + break; + case ENCODING_UNSET: + break; + } + + assert(map != NULL); + + /* count to-be-encoded-characters */ + for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) { + if (map[*ds]) { + switch(encoding) { + case ENCODING_REL_URI: + case ENCODING_REL_URI_PART: + d_len += 3; + break; + case ENCODING_HTML: + case ENCODING_MINIMAL_XML: + d_len += 6; + break; + case ENCODING_HEX: + d_len += 2; + break; + case ENCODING_UNSET: + break; + } + } else { + d_len ++; + } + } + + buffer_prepare_append(b, d_len); + + for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) { + if (map[*ds]) { + switch(encoding) { + case ENCODING_REL_URI: + case ENCODING_REL_URI_PART: + d[d_len++] = '%'; + d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F]; + d[d_len++] = hex_chars[(*ds) & 0x0F]; + break; + case ENCODING_HTML: + case ENCODING_MINIMAL_XML: + d[d_len++] = '&'; + d[d_len++] = '#'; + d[d_len++] = 'x'; + d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F]; + d[d_len++] = hex_chars[(*ds) & 0x0F]; + d[d_len++] = ';'; + break; + case ENCODING_HEX: + d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F]; + d[d_len++] = hex_chars[(*ds) & 0x0F]; + break; + case ENCODING_UNSET: + break; + } + } else { + d[d_len++] = *ds; + } + } + + /* terminate buffer and calculate new length */ + b->ptr[b->used + d_len - 1] = '\0'; + + b->used += d_len; + + return 0; +} + + +/* decodes url-special-chars inplace. + * replaces non-printable characters with '_' + */ + +static int buffer_urldecode_internal(buffer *url, int is_query) { + 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 (is_query && *src == '+') { + *dst = ' '; + } else 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_urldecode_path(buffer *url) { + return buffer_urldecode_internal(url, 0); +} + +int buffer_urldecode_query(buffer *url) { + return buffer_urldecode_internal(url, 1); +} + +/* Remove "/../", "//", "/./" parts from path. + * + * /blah/.. gets / + * /blah/../foo gets /foo + * /abc/./xyz gets /abc/xyz + * /abc//xyz gets /abc/xyz + * + * NOTE: src and dest can point to the same buffer, in which case, + * the operation is performed in-place. + */ + +int buffer_path_simplify(buffer *dest, buffer *src) +{ + int toklen; + char c, pre1; + char *start, *slash, *walk, *out; + unsigned short pre; + + if (src == NULL || src->ptr == NULL || dest == NULL) + return -1; + + if (src == dest) + buffer_prepare_append(dest, 1); + else + buffer_prepare_copy(dest, src->used + 1); + + walk = src->ptr; + start = dest->ptr; + out = dest->ptr; + slash = dest->ptr; + while (*walk == ' ') { + walk++; + } + + pre1 = *(walk++); + c = *(walk++); + pre = pre1; + if (pre1 != '/') { + pre = ('/' << 8) | pre1; + *(out++) = '/'; + } + *(out++) = pre1; + + if (pre1 == '\0') { + dest->used = (out - start) + 1; + return 0; + } + + while (1) { + if (c == '/' || c == '\0') { + toklen = out - slash; + if (toklen == 3 && pre == (('.' << 8) | '.')) { + out = slash; + if (out > start) { + out--; + while (out > start && *out != '/') { + out--; + } + } + + if (c == '\0') + out++; + } else if (toklen == 1 || pre == (('/' << 8) | '.')) { + out = slash; + if (c == '\0') + out++; + } + + slash = out; + } + + if (c == '\0') + break; + + pre1 = c; + pre = (pre << 8) | pre1; + c = *walk; + *out = pre1; + + out++; + walk++; + } + + *out = '\0'; + dest->used = (out - start) + 1; + + return 0; +} + +int light_isdigit(int c) { + return (c >= '0' && c <= '9'); +} + +int light_isxdigit(int c) { + if (light_isdigit(c)) return 1; + + c |= 32; + return (c >= 'a' && c <= 'f'); +} + +int light_isalpha(int c) { + c |= 32; + return (c >= 'a' && c <= 'z'); +} + +int light_isalnum(int c) { + return light_isdigit(c) || light_isalpha(c); +} + +int buffer_to_lower(buffer *b) { + char *c; + + if (b->used == 0) return 0; + + for (c = b->ptr; *c; c++) { + if (*c >= 'A' && *c <= 'Z') { + *c |= 32; + } + } + + return 0; +} + + +int buffer_to_upper(buffer *b) { + char *c; + + if (b->used == 0) return 0; + + for (c = b->ptr; *c; c++) { + if (*c >= 'a' && *c <= 'z') { + *c &= ~32; + } + } + + return 0; +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..3ca22e5 --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,130 @@ +#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; + +buffer_array* buffer_array_init(void); +void buffer_array_free(buffer_array *b); +void buffer_array_reset(buffer_array *b); +buffer *buffer_array_append_get_buffer(buffer_array *b); + +buffer* buffer_init(void); +buffer* buffer_init_buffer(buffer *b); +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 val); + +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_long_hex(buffer *b, unsigned long len); +int buffer_append_long(buffer *b, long val); + +#if defined(SIZEOF_LONG) && (SIZEOF_LONG == SIZEOF_OFF_T) +#define buffer_copy_off_t(x, y) buffer_copy_long(x, y) +#define buffer_append_off_t(x, y) buffer_append_long(x, y) +#else +int buffer_copy_off_t(buffer *b, off_t val); +int buffer_append_off_t(buffer *b, off_t val); +#endif + +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); + +typedef enum { + ENCODING_UNSET, + ENCODING_REL_URI, /* for coding a rel-uri (/with space/and%percent) nicely as part of a href */ + ENCODING_REL_URI_PART, /* same as ENC_REL_URL plus coding / too as %2F */ + ENCODING_HTML, /* & becomes & and so on */ + ENCODING_MINIMAL_XML, /* minimal encoding for xml */ + ENCODING_HEX /* encode string as hex */ +} buffer_encoding_t; + +int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding); + +int buffer_urldecode_path(buffer *url); +int buffer_urldecode_query(buffer *url); +int buffer_path_simplify(buffer *dest, buffer *src); + +int buffer_to_lower(buffer *b); +int buffer_to_upper(buffer *b); + +/** deprecated */ +int ltostr(char *buf, long val); +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, x ? sizeof(x) - 1 : 0 +#define CONST_BUF_LEN(x) x->ptr, x->used ? x->used - 1 : 0 + + +#define SEGFAULT() do { fprintf(stderr, "%s.%d: aborted\n", __FILE__, __LINE__); abort(); } while(0) +#define UNUSED(x) ( (void)(x) ) + +#endif diff --git a/src/chunk.c b/src/chunk.c new file mode 100644 index 0000000..3903428 --- /dev/null +++ b/src/chunk.c @@ -0,0 +1,385 @@ +/** + * the network chunk-API + * + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.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 = buffer_init(); + c->file.name = buffer_init(); + c->file.fd = -1; + c->file.mmap.start = MAP_FAILED; + c->next = NULL; + + return c; +} + +static void chunk_free(chunk *c) { + if (!c) return; + + buffer_free(c->mem); + buffer_free(c->file.name); + + free(c); +} + +static void chunk_reset(chunk *c) { + if (!c) return; + + buffer_reset(c->mem); + + if (c->file.is_temp && !buffer_is_empty(c->file.name)) { + unlink(c->file.name->ptr); + } + + buffer_reset(c->file.name); + + if (c->file.fd != -1) { + close(c->file.fd); + c->file.fd = -1; + } + if (MAP_FAILED != c->file.mmap.start) { + munmap(c->file.mmap.start, c->file.mmap.length); + c->file.mmap.start = MAP_FAILED; + } +} + + +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; + cq->unused_chunks--; + } + + 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) { + chunk *c; + /* move everything to the unused queue */ + + /* mark all read written */ + for (c = cq->first; c; c = c->next) { + switch(c->type) { + case MEM_CHUNK: + c->offset = c->mem->used - 1; + break; + case FILE_CHUNK: + c->offset = c->file.length; + break; + default: + break; + } + } + + chunkqueue_remove_finished_chunks(cq); + cq->bytes_in = 0; + cq->bytes_out = 0; +} + +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->file.name, fn); + c->file.start = offset; + c->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->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->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->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->mem); + + chunkqueue_prepend_chunk(cq, c); + + return c->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->mem); + + chunkqueue_append_chunk(cq, c); + + return c->mem; +} + +int chunkqueue_set_tempdirs(chunkqueue *cq, array *tempdirs) { + if (!cq) return -1; + + cq->tempdirs = tempdirs; + + return 0; +} + +chunk *chunkqueue_get_append_tempfile(chunkqueue *cq) { + chunk *c; + buffer *template = buffer_init_string("/var/tmp/lighttpd-upload-XXXXXX"); + + c = chunkqueue_get_unused_chunk(cq); + + c->type = FILE_CHUNK; + c->offset = 0; + + if (cq->tempdirs && cq->tempdirs->used) { + size_t i; + + /* we have several tempdirs, only if all of them fail we jump out */ + + for (i = 0; i < cq->tempdirs->used; i++) { + data_string *ds = (data_string *)cq->tempdirs->data[i]; + + buffer_copy_string_buffer(template, ds->value); + BUFFER_APPEND_SLASH(template); + BUFFER_APPEND_STRING_CONST(template, "lighttpd-upload-XXXXXX"); + + if (-1 != (c->file.fd = mkstemp(template->ptr))) { + /* only trigger the unlink if we created the temp-file successfully */ + c->file.is_temp = 1; + break; + } + } + } else { + if (-1 != (c->file.fd = mkstemp(template->ptr))) { + /* only trigger the unlink if we created the temp-file successfully */ + c->file.is_temp = 1; + } + } + + buffer_copy_string_buffer(c->file.name, template); + c->file.length = 0; + + chunkqueue_append_chunk(cq, c); + + buffer_free(template); + + return c; +} + + +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->mem->used ? c->mem->used - 1 : 0; + break; + case FILE_CHUNK: + len += c->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; +} + +int chunkqueue_remove_finished_chunks(chunkqueue *cq) { + chunk *c; + + for (c = cq->first; c; c = cq->first) { + int is_finished = 0; + + switch (c->type) { + case MEM_CHUNK: + if (c->offset == (off_t)c->mem->used - 1) is_finished = 1; + break; + case FILE_CHUNK: + if (c->offset == c->file.length) is_finished = 1; + break; + default: + break; + } + + if (!is_finished) break; + + chunk_reset(c); + + cq->first = c->next; + if (c == cq->last) cq->last = NULL; + + /* keep at max 4 chunks in the 'unused'-cache */ + if (cq->unused_chunks > 4) { + chunk_free(c); + } else { + c->next = cq->unused; + cq->unused = c; + cq->unused_chunks++; + } + } + + return 0; +} diff --git a/src/chunk.h b/src/chunk.h new file mode 100644 index 0000000..ddc8617 --- /dev/null +++ b/src/chunk.h @@ -0,0 +1,69 @@ +#ifndef _CHUNK_H_ +#define _CHUNK_H_ + +#include "buffer.h" +#include "array.h" + +typedef struct chunk { + enum { UNUSED_CHUNK, MEM_CHUNK, FILE_CHUNK } type; + + buffer *mem; /* either the storage of the mem-chunk or the read-ahead buffer */ + + struct { + /* filechunk */ + buffer *name; /* name of the file */ + off_t start; /* starting offset in the file */ + off_t length; /* octets to send from the starting offset */ + + int fd; + struct { + char *start; /* the start pointer of the mmap'ed area */ + size_t length; /* size of the mmap'ed area */ + off_t offset; /* start is <n> octet away from the start of the file */ + } mmap; + + int is_temp; /* file is temporary and will be deleted if on cleanup */ + } file; + + off_t offset; /* octets sent from this chunk + the size of the chunk is either + - mem-chunk: mem->used - 1 + - file-chunk: file.length + */ + + struct chunk *next; +} chunk; + +typedef struct { + chunk *first; + chunk *last; + + chunk *unused; + size_t unused_chunks; + + array *tempdirs; + + off_t bytes_in, bytes_out; +} chunkqueue; + +chunkqueue *chunkqueue_init(void); +int chunkqueue_set_tempdirs(chunkqueue *c, array *tempdirs); +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); +chunk * chunkqueue_get_append_tempfile(chunkqueue *cq); + +int chunkqueue_remove_finished_chunks(chunkqueue *cq); + +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/configfile-glue.c b/src/configfile-glue.c new file mode 100644 index 0000000..5c3c68b --- /dev/null +++ b/src/configfile-glue.c @@ -0,0 +1,445 @@ +#include <string.h> + +#include "base.h" +#include "buffer.h" +#include "array.h" +#include "log.h" +#include "plugin.h" + +/** + * like all glue code this file contains functions which + * are the external interface of lighttpd. The functions + * are used by the server itself and the plugins. + * + * The main-goal is to have a small library in the end + * which is linked against both and which will define + * the interface itself in the end. + * + */ + + +/* handle global options */ + +/* 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); + if (!da->is_index_key) { + /* the id's were generated automaticly, as we copy now we might have to renumber them + * this is used to prepend server.modules by mod_indexfiles as it has to be loaded + * before mod_fastcgi and friends */ + 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__, "sssd", + "the key of and array can only be a string or a integer, variable:", + cv[i].key, "type:", da->value->data[j]->type); + + 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__, "ssb", "get a string but expected a short:", cv[i].key, ds->value); + + return -1; + } + default: + log_error_write(srv, __FILE__, __LINE__, "ssds", "unexpected type for key:", cv[i].key, du->type, "expected a integer, range 0 ... 65535"); + return -1; + } + break; + case T_CONFIG_BOOLEAN: + if (du->type == TYPE_STRING) { + data_string *ds = (data_string *)du; + + if (buffer_is_equal_string(ds->value, CONST_STR_LEN("enable"))) { + *((unsigned short *)(cv[i].destination)) = 1; + } else if (buffer_is_equal_string(ds->value, CONST_STR_LEN("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; +} + +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); +} + +unsigned short sock_addr_get_port(sock_addr *addr) { +#ifdef HAVE_IPV6 + return ntohs(addr->plain.sa_family ? addr->ipv6.sin6_port : addr->ipv4.sin_port); +#else + return ntohs(addr->ipv4.sin_port); +#endif +} + +static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc); + +static cond_result_t config_check_cond_nocache(server *srv, connection *con, data_config *dc) { + buffer *l; + server_socket *srv_sock = con->srv_socket; + /* check parent first */ + if (dc->parent && dc->parent->context_ndx) { + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "sb", "go parent", dc->parent->key); + } + if (config_check_cond_cached(srv, con, dc->parent) == COND_RESULT_FALSE) { + return COND_RESULT_FALSE; + } + } + + if (dc->prev) { + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "sb", "go prev", dc->prev->key); + } + /* make sure prev is checked first */ + config_check_cond_cached(srv, con, dc->prev); + /* one of prev set me to FALSE */ + if (COND_RESULT_FALSE == con->cond_cache[dc->context_ndx].result) { + return COND_RESULT_FALSE; + } + } + + /* pass the rules */ + + switch (dc->comp) { + case COMP_HTTP_HOST: { + char *ck_colon = NULL, *val_colon = NULL; + + if (!buffer_is_empty(con->uri.authority)) { + + /* + * append server-port to the HTTP_POST if necessary + */ + + l = con->uri.authority; + + switch(dc->cond) { + case CONFIG_COND_NE: + case CONFIG_COND_EQ: + ck_colon = strchr(dc->string->ptr, ':'); + val_colon = strchr(l->ptr, ':'); + + if (ck_colon == val_colon) { + /* nothing to do with it */ + break; + } + if (ck_colon) { + /* condition "host:port" but client send "host" */ + buffer_copy_string_buffer(srv->cond_check_buf, l); + BUFFER_APPEND_STRING_CONST(srv->cond_check_buf, ":"); + buffer_append_long(srv->cond_check_buf, sock_addr_get_port(&(srv_sock->addr))); + l = srv->cond_check_buf; + } else if (!ck_colon) { + /* condition "host" but client send "host:port" */ + buffer_copy_string_len(srv->cond_check_buf, l->ptr, val_colon - l->ptr); + l = srv->cond_check_buf; + } + break; + default: + break; + } + } else { + l = NULL; + } + break; + } + case COMP_HTTP_REMOTEIP: { + char *nm_slash; + /* handle remoteip limitations + * + * "10.0.0.1" is provided for all comparisions + * + * only for == and != we support + * + * "10.0.0.1/24" + */ + + if ((dc->cond == CONFIG_COND_EQ || + dc->cond == CONFIG_COND_NE) && + (con->dst_addr.plain.sa_family == AF_INET) && + (NULL != (nm_slash = strchr(dc->string->ptr, '/')))) { + int nm_bits; + long nm; + char *err; + struct in_addr val_inp; + + if (*(nm_slash+1) == '\0') { + log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: no number after / ", dc->string); + + return COND_RESULT_FALSE; + } + + nm_bits = strtol(nm_slash + 1, &err, 10); + + if (*err) { + log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: non-digit found in netmask:", dc->string, *err); + + return COND_RESULT_FALSE; + } + + /* take IP convert to the native */ + buffer_copy_string_len(srv->cond_check_buf, dc->string->ptr, nm_slash - dc->string->ptr); +#ifdef __WIN32 + if (INADDR_NONE == (val_inp.s_addr = inet_addr(srv->cond_check_buf->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: ip addr is invalid:", srv->cond_check_buf); + + return COND_RESULT_FALSE; + } + +#else + if (0 == inet_aton(srv->cond_check_buf->ptr, &val_inp)) { + log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: ip addr is invalid:", srv->cond_check_buf); + + return COND_RESULT_FALSE; + } +#endif + + /* build netmask */ + nm = htonl(~((1 << (32 - nm_bits)) - 1)); + + if ((val_inp.s_addr & nm) == (con->dst_addr.ipv4.sin_addr.s_addr & nm)) { + return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE; + } else { + return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE; + } + } else { + l = con->dst_addr_buf; + } + break; + } + case COMP_HTTP_URL: + l = con->uri.path; + break; + + case COMP_SERVER_SOCKET: + l = srv_sock->srv_token; + break; + + case COMP_HTTP_REFERER: { + data_string *ds; + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Referer"))) { + l = ds->value; + } else { + l = srv->empty_string; + } + break; + } + case COMP_HTTP_COOKIE: { + data_string *ds; + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) { + l = ds->value; + } else { + l = srv->empty_string; + } + break; + } + case COMP_HTTP_USERAGENT: { + data_string *ds; + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "User-Agent"))) { + l = ds->value; + } else { + l = srv->empty_string; + } + break; + } + + default: + return COND_RESULT_FALSE; + } + + if (NULL == l) { + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "bsbs", dc->comp_key, + "(", l, ") compare to NULL"); + } + return COND_RESULT_FALSE; + } + + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "bsbsb", dc->comp_key, + "(", l, ") compare to ", dc->string); + } + switch(dc->cond) { + case CONFIG_COND_NE: + case CONFIG_COND_EQ: + if (buffer_is_equal(l, dc->string)) { + return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE; + } else { + return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE; + } + break; +#ifdef HAVE_PCRE_H + case CONFIG_COND_NOMATCH: + case CONFIG_COND_MATCH: { + cond_cache_t *cache = &con->cond_cache[dc->context_ndx]; + int n; + +#ifndef elementsof +#define elementsof(x) (sizeof(x) / sizeof(x[0])) +#endif + n = pcre_exec(dc->regex, dc->regex_study, l->ptr, l->used - 1, 0, 0, + cache->matches, elementsof(cache->matches)); + + if (n > 0) { + cache->patterncount = n; + cache->comp_value = l; + return (dc->cond == CONFIG_COND_MATCH) ? COND_RESULT_TRUE : COND_RESULT_FALSE; + } else { + /* cache is already cleared */ + return (dc->cond == CONFIG_COND_MATCH) ? COND_RESULT_FALSE : COND_RESULT_TRUE; + } + break; + } +#endif + default: + /* no way */ + break; + } + + return COND_RESULT_FALSE; +} + +static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc) { + cond_cache_t *caches = con->cond_cache; + + if (COND_RESULT_UNSET == caches[dc->context_ndx].result) { + if (COND_RESULT_TRUE == (caches[dc->context_ndx].result = config_check_cond_nocache(srv, con, dc))) { + if (dc->next) { + data_config *c; + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", + "setting remains of chaining to false"); + } + for (c = dc->next; c; c = c->next) { + caches[c->context_ndx].result = COND_RESULT_FALSE; + } + } + } + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "dss", dc->context_ndx, + "(uncached) result:", + caches[dc->context_ndx].result == COND_RESULT_TRUE ? "true" : "false"); + } + } else { + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "dss", dc->context_ndx, + "(cached) result:", + caches[dc->context_ndx].result == COND_RESULT_TRUE ? "true" : "false"); + } + } + return caches[dc->context_ndx].result; +} + +int config_check_cond(server *srv, connection *con, data_config *dc) { + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "=== start of condition block ==="); + } + return (config_check_cond_cached(srv, con, dc) == COND_RESULT_TRUE); +} + +int config_append_cond_match_buffer(connection *con, data_config *dc, buffer *buf, int n) +{ + cond_cache_t *cache = &con->cond_cache[dc->context_ndx]; + if (n > cache->patterncount) { + return 0; + } + + n <<= 1; /* n *= 2 */ + buffer_append_string_len(buf, + cache->comp_value->ptr + cache->matches[n], + cache->matches[n + 1] - cache->matches[n]); + return 1; +} + diff --git a/src/configfile.c b/src/configfile.c new file mode 100644 index 0000000..acb1e0a --- /dev/null +++ b/src/configfile.c @@ -0,0 +1,1194 @@ +#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" +#include "proc_open.h" + + +static int config_insert(server *srv) { + size_t i; + int ret = 0; + buffer *stat_cache_string; + + 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.force-lowercase-filenames", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 15 */ + { "debug.log-condition-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 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 */ + { "ssl.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 38 */ + + { "server.errorlog-use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 39 */ + { "server.range-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 40 */ + { "server.stat-cache-engine", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 41 */ + { "server.max-connections", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 42 */ + { "server.network-backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 43 */ + { "server.upload-dirs", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 44 */ + { "server.core-files", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 45 */ + + { "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 }, + { "server.force-lower-case-files", "use server.force-lowercase-filenames 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.errorlog_file; + 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); + + cv[39].destination = &(srv->srvconf.errorlog_use_syslog); + + stat_cache_string = buffer_init(); + cv[41].destination = stat_cache_string; + cv[43].destination = srv->srvconf.network_backend; + cv[44].destination = srv->srvconf.upload_tempdirs; + cv[45].destination = &(srv->srvconf.enable_cores); + + cv[42].destination = &(srv->srvconf.max_conns); + cv[12].destination = &(srv->srvconf.max_request_size); + srv->config_storage = calloc(1, 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->mimetypes = array_init(); + s->server_name = buffer_init(); + s->ssl_pemfile = buffer_init(); + s->ssl_ca_file = buffer_init(); + s->error_handler = buffer_init(); + s->server_tag = buffer_init(); + s->errorfile_prefix = buffer_init(); + s->max_keep_alive_requests = 16; + s->max_keep_alive_idle = 5; + 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->range_requests = 1; + s->force_lowercase_filenames = 0; + 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[2].destination = s->errorfile_prefix; + + cv[7].destination = s->server_tag; + cv[8].destination = &(s->use_ipv6); + + + /* 13 max-worker */ + cv[14].destination = s->document_root; + cv[15].destination = &(s->force_lowercase_filenames); + cv[16].destination = &(s->log_condition_handling); + 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); + cv[38].destination = s->ssl_ca_file; + cv[40].destination = &(s->range_requests); + + srv->config_storage[i] = s; + + if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) { + break; + } + } + + if (buffer_is_empty(stat_cache_string)) { + srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; + } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("simple"))) { + srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; + } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("fam"))) { + srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_FAM; + } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("disable"))) { + srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_NONE; + } else { + log_error_write(srv, __FILE__, __LINE__, "sb", + "server.stat-cache-engine can be one of \"disable\", \"simple\", \"fam\", but not:", stat_cache_string); + ret = HANDLER_ERROR; + } + + buffer_free(stat_cache_string); + + return ret; + +} + + +#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(max_keep_alive_requests); + PATCH(max_keep_alive_idle); + PATCH(max_read_idle); + PATCH(max_write_idle); + PATCH(use_xattr); + PATCH(error_handler); + PATCH(errorfile_prefix); + 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_condition_handling); + PATCH(log_file_not_found); + + PATCH(range_requests); + PATCH(force_lowercase_filenames); + PATCH(is_ssl); + + PATCH(ssl_pemfile); + PATCH(ssl_ca_file); + return 0; +} + +int config_patch_connection(server *srv, connection *con, comp_key_t comp) { + 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 (comp != dc->comp) 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.range-requests"))) { + PATCH(range_requests); + } 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.errorfile-prefix"))) { + PATCH(errorfile_prefix); + } 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.ca-file"))) { + PATCH(ssl_ca_file); + } 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-condition-handling"))) { + PATCH(log_condition_handling); + } 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.force-lowercase-filenames"))) { + PATCH(force_lowercase_filenames); + } 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; + + const buffer *source; + const char *input; + size_t offset; + size_t size; + + int line_pos; + int line; + + int in_key; + int in_brace; + int in_cond; +} tokenizer_t; + +#if 0 +static int tokenizer_open(server *srv, tokenizer_t *t, buffer *basedir, const char *fn) { + if (buffer_is_empty(basedir) && + (fn[0] == '/' || fn[0] == '\\') && + (fn[0] == '.' && (fn[1] == '/' || fn[1] == '\\'))) { + t->file = buffer_init_string(fn); + } else { + t->file = buffer_init_buffer(basedir); + buffer_append_string(t->file, fn); + } + + if (0 != stream_open(&(t->s), t->file)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening configfile ", t->file, "failed:", strerror(errno)); + buffer_free(t->file); + return -1; + } + + t->input = t->s.start; + t->offset = 0; + t->size = t->s.size; + t->line = 1; + t->line_pos = 1; + + t->in_key = 1; + t->in_brace = 0; + t->in_cond = 0; + return 0; +} + +static int tokenizer_close(server *srv, tokenizer_t *t) { + UNUSED(srv); + + buffer_free(t->file); + return stream_close(&(t->s)); +} +#endif +static int config_skip_newline(tokenizer_t *t) { + int skipped = 1; + assert(t->input[t->offset] == '\r' || t->input[t->offset] == '\n'); + if (t->input[t->offset] == '\r' && t->input[t->offset + 1] == '\n') { + skipped ++; + t->offset ++; + } + t->offset ++; + return skipped; +} + +static int config_skip_comment(tokenizer_t *t) { + int i; + assert(t->input[t->offset] == '#'); + for (i = 1; t->input[t->offset + i] && + (t->input[t->offset + i] != '\n' && t->input[t->offset + i] != '\r'); + i++); + t->offset += i; + return i; +} + +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]; + const 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__, "sbsdsds", + "source:", t->source, + "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__, "sbsdsds", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + "only =~ and == are allowed in the condition"); + return -1; + } + t->in_key = 1; + t->in_cond = 0; + } else if (t->in_key) { + tid = TK_ASSIGN; + + buffer_copy_string_len(token, t->input + t->offset, 1); + + t->offset++; + t->line_pos++; + } else { + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "source:", t->source, + "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__, "sbsdsds", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + "only !~ and != are allowed in the condition"); + return -1; + } + t->in_key = 1; + t->in_cond = 0; + } else { + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + "unexpected exclamation-marks: !"); + return -1; + } + + break; + case '\t': + case ' ': + t->offset++; + t->line_pos++; + break; + case '\n': + case '\r': + if (t->in_brace == 0) { + int done = 0; + while (!done && t->offset < t->size) { + switch (t->input[t->offset]) { + case '\r': + case '\n': + config_skip_newline(t); + t->line_pos = 1; + t->line++; + break; + + case '#': + t->line_pos += config_skip_comment(t); + break; + + case '\t': + case ' ': + t->offset++; + t->line_pos++; + break; + + default: + done = 1; + } + } + t->in_key = 1; + tid = TK_EOL; + buffer_copy_string(token, "(EOL)"); + } else { + config_skip_newline(t); + t->line_pos = 1; + t->line++; + } + 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__, "sbsdsds", + "source:", t->source, + "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 '+': + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + buffer_copy_string(token, "+="); + tid = TK_APPEND; + } else { + t->offset++; + tid = TK_PLUS; + buffer_copy_string(token, "+"); + } + break; + + case '{': + t->offset++; + + tid = TK_LCURLY; + + 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 '#': + t->line_pos += config_skip_comment(t); + + break; + default: + 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__, "sbsdsds", + "source:", t->source, + "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__, "sbsdsds", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + "unexpected EOF"); + + return -1; + } + } else { + /* 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] == '_' || /* for env.* */ + t->input[t->offset + i] == '-' + ); i++); + + if (i && t->input[t->offset + i]) { + buffer_copy_string_len(token, t->input + t->offset, i); + + if (strcmp(token->ptr, "include") == 0) { + tid = TK_INCLUDE; + } else if (strcmp(token->ptr, "include_shell") == 0) { + tid = TK_INCLUDE_SHELL; + } else if (strcmp(token->ptr, "global") == 0) { + tid = TK_GLOBAL; + } else if (strcmp(token->ptr, "else") == 0) { + tid = TK_ELSE; + } else { + tid = TK_LKEY; + } + + t->offset += i; + t->line_pos += i; + } else { + /* ERROR */ + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + "invalid character in variable name"); + return -1; + } + } + break; + } + } + + if (tid) { + *token_id = tid; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sbsdsdbdd", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + token, token->used - 1, tid); +#endif + + return 1; + } else if (t->offset < t->size) { + fprintf(stderr, "%s.%d: %d, %s\n", + __FILE__, __LINE__, + tid, token->ptr); + } + return 0; +} + +static int config_parse(server *srv, config_t *context, tokenizer_t *t) { + void *pParser; + int token_id; + buffer *token, *lasttoken; + int ret; + + pParser = configparserAlloc( malloc ); + lasttoken = buffer_init(); + token = buffer_init(); + while((1 == (ret = config_tokenizer(srv, t, &token_id, token))) && context->ok) { + buffer_copy_string_buffer(lasttoken, token); + configparser(pParser, token_id, token, context); + + token = buffer_init(); + } + buffer_free(token); + + if (ret != -1 && context->ok) { + /* add an EOL at EOF, better than say sorry */ + configparser(pParser, TK_EOL, buffer_init_string("(EOL)"), context); + if (context->ok) { + configparser(pParser, 0, NULL, context); + } + } + configparserFree(pParser, free); + + if (ret == -1) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "configfile parser failed:", lasttoken); + } else if (context->ok == 0) { + log_error_write(srv, __FILE__, __LINE__, "sbsdsdsb", + "source:", t->source, + "line:", t->line, "pos:", t->line_pos, + "parser failed somehow near here:", lasttoken); + ret = -1; + } + buffer_free(lasttoken); + + return ret == -1 ? -1 : 0; +} + +static int tokenizer_init(tokenizer_t *t, const buffer *source, const char *input, size_t size) { + + t->source = source; + t->input = input; + t->size = size; + t->offset = 0; + t->line = 1; + t->line_pos = 1; + + t->in_key = 1; + t->in_brace = 0; + t->in_cond = 0; + return 0; +} + +int config_parse_file(server *srv, config_t *context, const char *fn) { + tokenizer_t t; + stream s; + int ret; + buffer *filename; + + if (buffer_is_empty(context->basedir) && + (fn[0] == '/' || fn[0] == '\\') && + (fn[0] == '.' && (fn[1] == '/' || fn[1] == '\\'))) { + filename = buffer_init_string(fn); + } else { + filename = buffer_init_buffer(context->basedir); + buffer_append_string(filename, fn); + } + + if (0 != stream_open(&s, filename)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening configfile ", filename, "failed:", strerror(errno)); + ret = -1; + } else { + tokenizer_init(&t, filename, s.start, s.size); + ret = config_parse(srv, context, &t); + } + + stream_close(&s); + buffer_free(filename); + return ret; +} + +int config_parse_cmd(server *srv, config_t *context, const char *cmd) { + proc_handler_t proc; + tokenizer_t t; + int ret; + buffer *source; + buffer *out; + char oldpwd[PATH_MAX]; + + if (NULL == getcwd(oldpwd, sizeof(oldpwd))) { + log_error_write(srv, __FILE__, __LINE__, "s", + "cannot get cwd", strerror(errno)); + return -1; + } + + source = buffer_init_string(cmd); + out = buffer_init(); + + if (!buffer_is_empty(context->basedir)) { + chdir(context->basedir->ptr); + } + + if (0 != proc_open_buffer(&proc, cmd, NULL, out, NULL)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening", source, "failed:", strerror(errno)); + ret = -1; + } else { + tokenizer_init(&t, source, out->ptr, out->used); + ret = config_parse(srv, context, &t); + } + + buffer_free(source); + buffer_free(out); + chdir(oldpwd); + return ret; +} + +static void context_init(server *srv, config_t *context) { + context->srv = srv; + context->ok = 1; + context->configs_stack = array_init(); + context->configs_stack->is_weakref = 1; + context->basedir = buffer_init(); +} + +static void context_free(config_t *context) { + array_free(context->configs_stack); + buffer_free(context->basedir); +} + +int config_read(server *srv, const char *fn) { + config_t context; + data_config *dc; + data_integer *dpid; + data_string *dcwd; + int ret; + char *pos; + data_array *modules; + + context_init(srv, &context); + context.all_configs = srv->config_context; + + pos = strrchr(fn, +#ifdef __WIN32 + '\\' +#else + '/' +#endif + ); + if (pos) { + buffer_copy_string_len(context.basedir, fn, pos - fn + 1); + fn = pos + 1; + } + + dc = data_config_init(); + buffer_copy_string(dc->key, "global"); + + assert(context.all_configs->used == 0); + dc->context_ndx = context.all_configs->used; + array_insert_unique(context.all_configs, (data_unset *)dc); + context.current = dc; + + /* default context */ + srv->config = dc->value; + dpid = data_integer_init(); + dpid->value = getpid(); + buffer_copy_string(dpid->key, "var.PID"); + array_insert_unique(srv->config, (data_unset *)dpid); + + dcwd = data_string_init(); + buffer_prepare_copy(dcwd->value, 1024); + if (NULL != getcwd(dcwd->value->ptr, dcwd->value->size - 1)) { + dcwd->value->used = strlen(dcwd->value->ptr) + 1; + buffer_copy_string(dcwd->key, "var.CWD"); + array_insert_unique(srv->config, (data_unset *)dcwd); + } + + ret = config_parse_file(srv, &context, fn); + + /* remains nothing if parser is ok */ + assert(!(0 == ret && context.ok && 0 != context.configs_stack->used)); + context_free(&context); + + if (0 != ret) { + return ret; + } + + if (NULL != (dc = (data_config *)array_get_element(srv->config_context, "global"))) { + srv->config = dc->value; + } else { + return -1; + } + + if (NULL != (modules = (data_array *)array_get_element(srv->config, "server.modules"))) { + data_string *ds; + data_array *prepends; + + if (modules->type != TYPE_ARRAY) { + fprintf(stderr, "server.modules must be an array"); + return -1; + } + + prepends = data_array_init(); + + /* prepend default modules */ + if (NULL == array_get_element(modules->value, "mod_indexfile")) { + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_indexfile"); + array_insert_unique(prepends->value, (data_unset *)ds); + } + + prepends = (data_array *)configparser_merge_data((data_unset *)prepends, (data_unset *)modules); + buffer_copy_string_buffer(prepends->key, modules->key); + array_replace(srv->config, (data_unset *)prepends); + modules->free((data_unset *)modules); + modules = prepends; + + /* append default modules */ + if (NULL == array_get_element(modules->value, "mod_dirlisting")) { + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_dirlisting"); + array_insert_unique(modules->value, (data_unset *)ds); + } + + if (NULL == array_get_element(modules->value, "mod_staticfile")) { + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_staticfile"); + array_insert_unique(modules->value, (data_unset *)ds); + } + } else { + data_string *ds; + + modules = data_array_init(); + + /* server.modules is not set */ + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_indexfile"); + array_insert_unique(modules->value, (data_unset *)ds); + + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_dirlisting"); + array_insert_unique(modules->value, (data_unset *)ds); + + ds = data_string_init(); + buffer_copy_string(ds->value, "mod_staticfile"); + array_insert_unique(modules->value, (data_unset *)ds); + + buffer_copy_string(modules->key, "server.modules"); + array_insert_unique(srv->config, (data_unset *)modules); + } + + + if (0 != config_insert(srv)) { + return -1; + } + + return 0; +} + +int config_set_defaults(server *srv) { + size_t i; + specific_config *s = srv->config_storage[0]; + struct stat st1, st2; + + 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" }, + { FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue" }, +#endif + { FDEVENT_HANDLER_UNSET, NULL } + }; + + + if (buffer_is_empty(s->document_root)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "a default document-root has to be set"); + + return -1; + } + + if (buffer_is_empty(srv->srvconf.changeroot)) { + if (-1 == stat(s->document_root->ptr, &st1)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "base-docroot doesn't exist:", + s->document_root); + return -1; + } + + } else { + buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.changeroot); + buffer_append_string_buffer(srv->tmp_buf, s->document_root); + + if (-1 == stat(srv->tmp_buf->ptr, &st1)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "base-docroot doesn't exist:", + srv->tmp_buf); + return -1; + } + + } + + buffer_copy_string_buffer(srv->tmp_buf, s->document_root); + + buffer_to_lower(srv->tmp_buf); + + if (0 == stat(srv->tmp_buf->ptr, &st1)) { + int is_lower = 0; + + is_lower = buffer_is_equal(srv->tmp_buf, s->document_root); + + /* lower-case existed, check upper-case */ + buffer_copy_string_buffer(srv->tmp_buf, s->document_root); + + buffer_to_upper(srv->tmp_buf); + + /* we have to handle the special case that upper and lower-casing results in the same filename + * as in server.document-root = "/" or "/12345/" */ + + if (is_lower && buffer_is_equal(srv->tmp_buf, s->document_root)) { + /* lower-casing and upper-casing didn't result in + * an other filename, no need to stat(), + * just assume it is case-sensitive. */ + + s->force_lowercase_filenames = 0; + } else if (0 == stat(srv->tmp_buf->ptr, &st2)) { + + /* upper case exists too, doesn't the FS handle this ? */ + + /* upper and lower have the same inode -> case-insensitve FS */ + + if (st1.st_ino == st2.st_ino) { + /* upper and lower have the same inode -> case-insensitve FS */ + + s->force_lowercase_filenames = 1; + } + } + } + + 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 (buffer_is_empty(s->ssl_pemfile)) { + /* 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 0000000..600297f --- /dev/null +++ b/src/configfile.h @@ -0,0 +1,24 @@ +#ifndef _CONFIG_PARSER_H_ +#define _CONFIG_PARSER_H_ + +#include "array.h" +#include "buffer.h" +#include "server.h" + +typedef struct { + server *srv; + int ok; + array *all_configs; + array *configs_stack; /* to parse nested block */ + data_config *current; /* current started with { */ + buffer *basedir; +} 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); +int config_parse_file(server *srv, config_t *context, const char *fn); +int config_parse_cmd(server *srv, config_t *context, const char *cmd); +data_unset *configparser_merge_data(data_unset *op1, const data_unset *op2); + +#endif diff --git a/src/configparser.c b/src/configparser.c new file mode 100644 index 0000000..c2a3960 --- /dev/null +++ b/src/configparser.c @@ -0,0 +1,1568 @@ +/* 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> +#line 5 "./configparser.y" + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "configfile.h" +#include "buffer.h" +#include "array.h" + +static void configparser_push(config_t *ctx, data_config *dc, int isnew) { + if (isnew) { + dc->context_ndx = ctx->all_configs->used; + assert(dc->context_ndx > ctx->current->context_ndx); + array_insert_unique(ctx->all_configs, (data_unset *)dc); + dc->parent = ctx->current; + array_insert_unique(dc->parent->childs, (data_unset *)dc); + } + array_insert_unique(ctx->configs_stack, (data_unset *)ctx->current); + ctx->current = dc; +} + +static data_config *configparser_pop(config_t *ctx) { + data_config *old = ctx->current; + ctx->current = (data_config *) array_pop(ctx->configs_stack); + return old; +} + +/* return a copied variable */ +static data_unset *configparser_get_variable(config_t *ctx, const buffer *key) { + if (strncmp(key->ptr, "env.", sizeof("env.") - 1) == 0) { + char *env; + + if (NULL != (env = getenv(key->ptr + 4))) { + data_string *ds; + ds = data_string_init(); + buffer_append_string(ds->value, env); + return (data_unset *)ds; + } + + fprintf(stderr, "Undefined env variable: %s\n", key->ptr + 4); + ctx->ok = 0; + + return NULL; + } else { + data_unset *du; + data_config *dc; + +#if 0 + fprintf(stderr, "get var %s\n", key->ptr); +#endif + for (dc = ctx->current; dc; dc = dc->parent) { +#if 0 + fprintf(stderr, "get var on block: %s\n", dc->key->ptr); + array_print(dc->value, 0); +#endif + if (NULL != (du = array_get_element(dc->value, key->ptr))) { + return du->copy(du); + } + } + fprintf(stderr, "Undefined config variable: %s\n", key->ptr); + ctx->ok = 0; + return NULL; + } +} + +/* op1 is to be eat/return by this function, op1->key is not cared + op2 is left untouch, unreferenced + */ +data_unset *configparser_merge_data(data_unset *op1, const data_unset *op2) { + /* type mismatch */ + if (op1->type != op2->type) { + if (op1->type == TYPE_STRING && op2->type == TYPE_INTEGER) { + data_string *ds = (data_string *)op1; + buffer_append_long(ds->value, ((data_integer*)op2)->value); + return op1; + } else if (op1->type == TYPE_INTEGER && op2->type == TYPE_STRING) { + data_string *ds = data_string_init(); + buffer_append_long(ds->value, ((data_integer*)op1)->value); + buffer_append_string_buffer(ds->value, ((data_string*)op2)->value); + op1->free(op1); + return (data_unset *)ds; + } else { + fprintf(stderr, "data type mismatch, cannot be merge\n"); + op1->free(op1); + return NULL; + } + } + + switch (op1->type) { + case TYPE_STRING: + buffer_append_string_buffer(((data_string *)op1)->value, ((data_string *)op2)->value); + break; + case TYPE_INTEGER: + ((data_integer *)op1)->value += ((data_integer *)op2)->value; + break; + case TYPE_ARRAY: { + array *dst = ((data_array *)op1)->value; + array *src = ((data_array *)op2)->value; + data_unset *du; + size_t i; + + for (i = 0; i < src->used; i ++) { + du = (data_unset *)src->data[i]; + if (du) { + array_insert_unique(dst, du->copy(du)); + } + } + break; + default: + assert(0); + break; + } + } + return op1; +} + + +#line 128 "configparser.c" +/* 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. +** configparserTOKENTYPE 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 configparserTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. +** configparserARG_SDECL A static variable declaration for the %extra_argument +** configparserARG_PDECL A parameter declaration for the %extra_argument +** configparserARG_STORE Code to store %extra_argument into yypParser +** configparserARG_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 YYCODETYPE unsigned char +#define YYNOCODE 48 +#define YYACTIONTYPE unsigned char +#define configparserTOKENTYPE buffer * +typedef union { + configparserTOKENTYPE yy0; + config_cond_t yy27; + array * yy40; + data_unset * yy41; + buffer * yy43; + data_config * yy78; + int yy95; +} YYMINORTYPE; +#define YYSTACKDEPTH 100 +#define configparserARG_SDECL config_t *ctx; +#define configparserARG_PDECL ,config_t *ctx +#define configparserARG_FETCH config_t *ctx = yypParser->ctx +#define configparserARG_STORE yypParser->ctx = ctx +#define YYNSTATE 62 +#define YYNRULE 39 +#define YYERRORSYMBOL 26 +#define YYERRSYMDT yy95 +#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. +*/ +static YYACTIONTYPE yy_action[] = { + /* 0 */ 2, 3, 4, 5, 13, 14, 62, 15, 7, 44, + /* 10 */ 20, 86, 16, 45, 28, 48, 40, 10, 39, 25, + /* 20 */ 22, 49, 45, 8, 15, 102, 1, 20, 28, 18, + /* 30 */ 57, 59, 19, 25, 22, 39, 19, 61, 98, 45, + /* 40 */ 20, 6, 23, 24, 26, 28, 35, 57, 59, 12, + /* 50 */ 25, 22, 28, 27, 36, 87, 29, 25, 22, 33, + /* 60 */ 15, 30, 31, 20, 28, 38, 9, 17, 37, 25, + /* 70 */ 22, 39, 42, 43, 10, 45, 11, 53, 54, 55, + /* 80 */ 56, 28, 52, 57, 59, 34, 25, 22, 28, 27, + /* 90 */ 32, 88, 41, 25, 22, 33, 28, 48, 46, 28, + /* 100 */ 48, 25, 22, 58, 25, 22, 60, 21, 19, 47, + /* 110 */ 51, 50, 25, 22, 88, 88, 93, +}; +static YYCODETYPE yy_lookahead[] = { + /* 0 */ 29, 30, 31, 32, 33, 34, 0, 1, 44, 38, + /* 10 */ 4, 15, 41, 16, 35, 36, 45, 46, 12, 40, + /* 20 */ 41, 42, 16, 15, 1, 27, 28, 4, 35, 36, + /* 30 */ 24, 25, 5, 40, 41, 12, 5, 14, 11, 16, + /* 40 */ 4, 1, 6, 7, 8, 35, 36, 24, 25, 28, + /* 50 */ 40, 41, 35, 36, 37, 15, 39, 40, 41, 42, + /* 60 */ 1, 9, 10, 4, 35, 36, 38, 2, 3, 40, + /* 70 */ 41, 12, 28, 14, 46, 16, 13, 20, 21, 22, + /* 80 */ 23, 35, 36, 24, 25, 11, 40, 41, 35, 36, + /* 90 */ 37, 13, 13, 40, 41, 42, 35, 36, 17, 35, + /* 100 */ 36, 40, 41, 42, 40, 41, 42, 35, 5, 18, + /* 110 */ 43, 19, 40, 41, 47, 47, 13, +}; +#define YY_SHIFT_USE_DFLT (-5) +static signed char yy_shift_ofst[] = { + /* 0 */ -5, 6, -5, -5, -5, 40, -4, 8, -3, -5, + /* 10 */ 63, -5, 23, -5, -5, -5, 65, 36, 31, 36, + /* 20 */ -5, -5, -5, -5, -5, -5, 36, 27, -5, 52, + /* 30 */ -5, 36, -5, 74, 36, 31, -5, 36, 31, 78, + /* 40 */ 79, -5, 59, -5, -5, 81, 91, 36, 31, 92, + /* 50 */ 57, 36, 103, -5, -5, -5, -5, 36, -5, 36, + /* 60 */ -5, -5, +}; +#define YY_REDUCE_USE_DFLT (-37) +static signed char yy_reduce_ofst[] = { + /* 0 */ -2, -29, -37, -37, -37, -36, -37, -37, 28, -37, + /* 10 */ -37, 21, -29, -37, -37, -37, -37, -7, -37, 72, + /* 20 */ -37, -37, -37, -37, -37, -37, 17, -37, -37, -37, + /* 30 */ -37, 53, -37, -37, 10, -37, -37, 29, -37, -37, + /* 40 */ -37, 44, -29, -37, -37, -37, -37, -21, -37, -37, + /* 50 */ 67, 46, -37, -37, -37, -37, -37, 61, -37, 64, + /* 60 */ -37, -37, +}; +static YYACTIONTYPE yy_default[] = { + /* 0 */ 64, 101, 63, 65, 66, 101, 67, 101, 101, 90, + /* 10 */ 101, 64, 101, 68, 69, 70, 101, 101, 71, 101, + /* 20 */ 73, 74, 76, 77, 78, 79, 101, 84, 75, 101, + /* 30 */ 80, 82, 81, 101, 101, 85, 83, 101, 72, 101, + /* 40 */ 101, 64, 101, 89, 91, 101, 101, 101, 98, 101, + /* 50 */ 101, 101, 101, 94, 95, 96, 97, 101, 99, 101, + /* 60 */ 100, 92, +}; +#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 */ + configparserARG_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 configparserTrace(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[] = { + "$", "EOL", "ASSIGN", "APPEND", + "LKEY", "PLUS", "STRING", "INTEGER", + "LPARAN", "RPARAN", "COMMA", "ARRAY_ASSIGN", + "GLOBAL", "LCURLY", "RCURLY", "ELSE", + "DOLLAR", "SRVVARNAME", "LBRACKET", "RBRACKET", + "EQ", "MATCH", "NE", "NOMATCH", + "INCLUDE", "INCLUDE_SHELL", "error", "input", + "metalines", "metaline", "varline", "global", + "condlines", "include", "include_shell", "value", + "expression", "aelement", "condline", "aelements", + "array", "key", "stringop", "cond", + "eols", "globalstart", "context", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *yyRuleName[] = { + /* 0 */ "input ::= metalines", + /* 1 */ "metalines ::= metalines metaline", + /* 2 */ "metalines ::=", + /* 3 */ "metaline ::= varline", + /* 4 */ "metaline ::= global", + /* 5 */ "metaline ::= condlines EOL", + /* 6 */ "metaline ::= include", + /* 7 */ "metaline ::= include_shell", + /* 8 */ "metaline ::= EOL", + /* 9 */ "varline ::= key ASSIGN expression", + /* 10 */ "varline ::= key APPEND expression", + /* 11 */ "key ::= LKEY", + /* 12 */ "expression ::= expression PLUS value", + /* 13 */ "expression ::= value", + /* 14 */ "value ::= key", + /* 15 */ "value ::= STRING", + /* 16 */ "value ::= INTEGER", + /* 17 */ "value ::= array", + /* 18 */ "array ::= LPARAN aelements RPARAN", + /* 19 */ "aelements ::= aelements COMMA aelement", + /* 20 */ "aelements ::= aelements COMMA", + /* 21 */ "aelements ::= aelement", + /* 22 */ "aelement ::= expression", + /* 23 */ "aelement ::= stringop ARRAY_ASSIGN expression", + /* 24 */ "eols ::= EOL", + /* 25 */ "eols ::=", + /* 26 */ "globalstart ::= GLOBAL", + /* 27 */ "global ::= globalstart LCURLY metalines RCURLY", + /* 28 */ "condlines ::= condlines eols ELSE condline", + /* 29 */ "condlines ::= condline", + /* 30 */ "condline ::= context LCURLY metalines RCURLY", + /* 31 */ "context ::= DOLLAR SRVVARNAME LBRACKET stringop RBRACKET cond expression", + /* 32 */ "cond ::= EQ", + /* 33 */ "cond ::= MATCH", + /* 34 */ "cond ::= NE", + /* 35 */ "cond ::= NOMATCH", + /* 36 */ "stringop ::= expression", + /* 37 */ "include ::= INCLUDE stringop", + /* 38 */ "include_shell ::= INCLUDE_SHELL stringop", +}; +#endif /* NDEBUG */ + +/* +** This function returns the symbolic name associated with a token +** value. +*/ +const char *configparserTokenName(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 configparser and configparserFree. +*/ +void *configparserAlloc(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. + */ + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: +#line 160 "./configparser.y" +{ buffer_free((yypminor->yy0)); } +#line 533 "configparser.c" + break; + case 35: +#line 151 "./configparser.y" +{ (yypminor->yy41)->free((yypminor->yy41)); } +#line 538 "configparser.c" + break; + case 36: +#line 152 "./configparser.y" +{ (yypminor->yy41)->free((yypminor->yy41)); } +#line 543 "configparser.c" + break; + case 37: +#line 153 "./configparser.y" +{ (yypminor->yy41)->free((yypminor->yy41)); } +#line 548 "configparser.c" + break; + case 39: +#line 154 "./configparser.y" +{ array_free((yypminor->yy40)); } +#line 553 "configparser.c" + break; + case 40: +#line 155 "./configparser.y" +{ array_free((yypminor->yy40)); } +#line 558 "configparser.c" + break; + case 41: +#line 156 "./configparser.y" +{ buffer_free((yypminor->yy43)); } +#line 563 "configparser.c" + break; + case 42: +#line 157 "./configparser.y" +{ buffer_free((yypminor->yy43)); } +#line 568 "configparser.c" + break; + 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 configparserAlloc. +** <li> A pointer to a function used to reclaim memory obtained +** from malloc. +** </ul> +*/ +void configparserFree( + 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 ){ + configparserARG_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 */ + configparserARG_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[] = { + { 27, 1 }, + { 28, 2 }, + { 28, 0 }, + { 29, 1 }, + { 29, 1 }, + { 29, 2 }, + { 29, 1 }, + { 29, 1 }, + { 29, 1 }, + { 30, 3 }, + { 30, 3 }, + { 41, 1 }, + { 36, 3 }, + { 36, 1 }, + { 35, 1 }, + { 35, 1 }, + { 35, 1 }, + { 35, 1 }, + { 40, 3 }, + { 39, 3 }, + { 39, 2 }, + { 39, 1 }, + { 37, 1 }, + { 37, 3 }, + { 44, 1 }, + { 44, 0 }, + { 45, 1 }, + { 31, 4 }, + { 32, 4 }, + { 32, 1 }, + { 38, 4 }, + { 46, 7 }, + { 43, 1 }, + { 43, 1 }, + { 43, 1 }, + { 43, 1 }, + { 42, 1 }, + { 33, 2 }, + { 34, 2 }, +}; + +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 */ + configparserARG_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; + */ + case 0: + /* No destructor defined for metalines */ + break; + case 1: + /* No destructor defined for metalines */ + /* No destructor defined for metaline */ + break; + case 2: + break; + case 3: + /* No destructor defined for varline */ + break; + case 4: + /* No destructor defined for global */ + break; + case 5: +#line 134 "./configparser.y" +{ yymsp[-1].minor.yy78 = NULL; } +#line 837 "configparser.c" + yy_destructor(1,&yymsp[0].minor); + break; + case 6: + /* No destructor defined for include */ + break; + case 7: + /* No destructor defined for include_shell */ + break; + case 8: + yy_destructor(1,&yymsp[0].minor); + break; + case 9: +#line 162 "./configparser.y" +{ + buffer_copy_string_buffer(yymsp[0].minor.yy41->key, yymsp[-2].minor.yy43); + if (NULL == array_get_element(ctx->current->value, yymsp[0].minor.yy41->key->ptr)) { + array_insert_unique(ctx->current->value, yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; + } else { + fprintf(stderr, "Duplicate config variable in conditional %d %s: %s\n", + ctx->current->context_ndx, + ctx->current->key->ptr, yymsp[0].minor.yy41->key->ptr); + ctx->ok = 0; + yymsp[0].minor.yy41->free(yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; + } + buffer_free(yymsp[-2].minor.yy43); + yymsp[-2].minor.yy43 = NULL; +} +#line 867 "configparser.c" + yy_destructor(2,&yymsp[-1].minor); + break; + case 10: +#line 179 "./configparser.y" +{ + array *vars = ctx->current->value; + data_unset *du; + + if (NULL != (du = array_get_element(vars, yymsp[-2].minor.yy43->ptr))) { + /* exists in current block */ + du = configparser_merge_data(du, yymsp[0].minor.yy41); + if (NULL == du) { + ctx->ok = 0; + } + else { + buffer_copy_string_buffer(du->key, yymsp[-2].minor.yy43); + array_replace(vars, du); + } + } else if (NULL != (du = configparser_get_variable(ctx, yymsp[-2].minor.yy43))) { + du = configparser_merge_data(du, yymsp[0].minor.yy41); + if (NULL == du) { + ctx->ok = 0; + } + else { + buffer_copy_string_buffer(du->key, yymsp[-2].minor.yy43); + array_insert_unique(ctx->current->value, du); + } + } else { + fprintf(stderr, "Undefined config variable in conditional %d %s: %s\n", + ctx->current->context_ndx, + ctx->current->key->ptr, yymsp[-2].minor.yy43->ptr); + ctx->ok = 0; + } + buffer_free(yymsp[-2].minor.yy43); + yymsp[-2].minor.yy43 = NULL; + yymsp[0].minor.yy41->free(yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; +} +#line 906 "configparser.c" + yy_destructor(3,&yymsp[-1].minor); + break; + case 11: +#line 214 "./configparser.y" +{ + if (strchr(yymsp[0].minor.yy0->ptr, '.') == NULL) { + yygotominor.yy43 = buffer_init_string("var."); + buffer_append_string_buffer(yygotominor.yy43, yymsp[0].minor.yy0); + buffer_free(yymsp[0].minor.yy0); + yymsp[0].minor.yy0 = NULL; + } else { + yygotominor.yy43 = yymsp[0].minor.yy0; + yymsp[0].minor.yy0 = NULL; + } +} +#line 922 "configparser.c" + break; + case 12: +#line 226 "./configparser.y" +{ + yygotominor.yy41 = configparser_merge_data(yymsp[-2].minor.yy41, yymsp[0].minor.yy41); + if (NULL == yygotominor.yy41) { + ctx->ok = 0; + } + yymsp[-2].minor.yy41 = NULL; + yymsp[0].minor.yy41->free(yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; +} +#line 935 "configparser.c" + yy_destructor(5,&yymsp[-1].minor); + break; + case 13: +#line 236 "./configparser.y" +{ + yygotominor.yy41 = yymsp[0].minor.yy41; + yymsp[0].minor.yy41 = NULL; +} +#line 944 "configparser.c" + break; + case 14: +#line 241 "./configparser.y" +{ + yygotominor.yy41 = configparser_get_variable(ctx, yymsp[0].minor.yy43); + if (!yygotominor.yy41) { + /* make a dummy so it won't crash */ + yygotominor.yy41 = (data_unset *)data_string_init(); + } + buffer_free(yymsp[0].minor.yy43); + yymsp[0].minor.yy43 = NULL; +} +#line 957 "configparser.c" + break; + case 15: +#line 251 "./configparser.y" +{ + yygotominor.yy41 = (data_unset *)data_string_init(); + buffer_copy_string_buffer(((data_string *)(yygotominor.yy41))->value, yymsp[0].minor.yy0); + buffer_free(yymsp[0].minor.yy0); + yymsp[0].minor.yy0 = NULL; +} +#line 967 "configparser.c" + break; + case 16: +#line 258 "./configparser.y" +{ + yygotominor.yy41 = (data_unset *)data_integer_init(); + ((data_integer *)(yygotominor.yy41))->value = strtol(yymsp[0].minor.yy0->ptr, NULL, 10); + buffer_free(yymsp[0].minor.yy0); + yymsp[0].minor.yy0 = NULL; +} +#line 977 "configparser.c" + break; + case 17: +#line 264 "./configparser.y" +{ + yygotominor.yy41 = (data_unset *)data_array_init(); + array_free(((data_array *)(yygotominor.yy41))->value); + ((data_array *)(yygotominor.yy41))->value = yymsp[0].minor.yy40; + yymsp[0].minor.yy40 = NULL; +} +#line 987 "configparser.c" + break; + case 18: +#line 270 "./configparser.y" +{ + yygotominor.yy40 = yymsp[-1].minor.yy40; + yymsp[-1].minor.yy40 = NULL; +} +#line 995 "configparser.c" + yy_destructor(8,&yymsp[-2].minor); + yy_destructor(9,&yymsp[0].minor); + break; + case 19: +#line 275 "./configparser.y" +{ + if (buffer_is_empty(yymsp[0].minor.yy41->key) || + NULL == array_get_element(yymsp[-2].minor.yy40, yymsp[0].minor.yy41->key->ptr)) { + array_insert_unique(yymsp[-2].minor.yy40, yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; + } else { + fprintf(stderr, "Duplicate array-key: %s\n", + yymsp[0].minor.yy41->key->ptr); + ctx->ok = 0; + yymsp[0].minor.yy41->free(yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; + } + + yygotominor.yy40 = yymsp[-2].minor.yy40; + yymsp[-2].minor.yy40 = NULL; +} +#line 1017 "configparser.c" + yy_destructor(10,&yymsp[-1].minor); + break; + case 20: +#line 292 "./configparser.y" +{ + yygotominor.yy40 = yymsp[-1].minor.yy40; + yymsp[-1].minor.yy40 = NULL; +} +#line 1026 "configparser.c" + yy_destructor(10,&yymsp[0].minor); + break; + case 21: +#line 297 "./configparser.y" +{ + yygotominor.yy40 = array_init(); + array_insert_unique(yygotominor.yy40, yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; +} +#line 1036 "configparser.c" + break; + case 22: +#line 303 "./configparser.y" +{ + yygotominor.yy41 = yymsp[0].minor.yy41; + yymsp[0].minor.yy41 = NULL; +} +#line 1044 "configparser.c" + break; + case 23: +#line 307 "./configparser.y" +{ + buffer_copy_string_buffer(yymsp[0].minor.yy41->key, yymsp[-2].minor.yy43); + buffer_free(yymsp[-2].minor.yy43); + yymsp[-2].minor.yy43 = NULL; + + yygotominor.yy41 = yymsp[0].minor.yy41; + yymsp[0].minor.yy41 = NULL; +} +#line 1056 "configparser.c" + yy_destructor(11,&yymsp[-1].minor); + break; + case 24: + yy_destructor(1,&yymsp[0].minor); + break; + case 25: + break; + case 26: +#line 319 "./configparser.y" +{ + data_config *dc; + dc = (data_config *)array_get_element(ctx->srv->config_context, "global"); + assert(dc); + configparser_push(ctx, dc, 0); +} +#line 1072 "configparser.c" + yy_destructor(12,&yymsp[0].minor); + break; + case 27: +#line 326 "./configparser.y" +{ + data_config *cur; + + cur = ctx->current; + configparser_pop(ctx); + + assert(cur && ctx->current); + + yygotominor.yy0 = cur; +} +#line 1087 "configparser.c" + /* No destructor defined for globalstart */ + yy_destructor(13,&yymsp[-2].minor); + /* No destructor defined for metalines */ + yy_destructor(14,&yymsp[0].minor); + break; + case 28: +#line 337 "./configparser.y" +{ + assert(yymsp[-3].minor.yy78->context_ndx < yymsp[0].minor.yy78->context_ndx); + yymsp[0].minor.yy78->prev = yymsp[-3].minor.yy78; + yymsp[-3].minor.yy78->next = yymsp[0].minor.yy78; + yygotominor.yy78 = yymsp[0].minor.yy78; + yymsp[-3].minor.yy78 = NULL; + yymsp[0].minor.yy78 = NULL; +} +#line 1103 "configparser.c" + /* No destructor defined for eols */ + yy_destructor(15,&yymsp[-1].minor); + break; + case 29: +#line 346 "./configparser.y" +{ + yygotominor.yy78 = yymsp[0].minor.yy78; + yymsp[0].minor.yy78 = NULL; +} +#line 1113 "configparser.c" + break; + case 30: +#line 351 "./configparser.y" +{ + data_config *cur; + + cur = ctx->current; + configparser_pop(ctx); + + assert(cur && ctx->current); + + yygotominor.yy78 = cur; +} +#line 1127 "configparser.c" + /* No destructor defined for context */ + yy_destructor(13,&yymsp[-2].minor); + /* No destructor defined for metalines */ + yy_destructor(14,&yymsp[0].minor); + break; + case 31: +#line 362 "./configparser.y" +{ + data_config *dc; + buffer *b, *rvalue, *op; + + if (ctx->ok && yymsp[0].minor.yy41->type != TYPE_STRING) { + fprintf(stderr, "rvalue must be string"); + ctx->ok = 0; + } + + switch(yymsp[-1].minor.yy27) { + case CONFIG_COND_NE: + op = buffer_init_string("!="); + break; + case CONFIG_COND_EQ: + op = buffer_init_string("=="); + break; + case CONFIG_COND_NOMATCH: + op = buffer_init_string("!~"); + break; + case CONFIG_COND_MATCH: + op = buffer_init_string("=~"); + break; + default: + assert(0); + return; + } + + b = buffer_init(); + buffer_copy_string_buffer(b, ctx->current->key); + buffer_append_string(b, "/"); + buffer_append_string_buffer(b, yymsp[-5].minor.yy0); + buffer_append_string_buffer(b, yymsp[-3].minor.yy43); + buffer_append_string_buffer(b, op); + rvalue = ((data_string*)yymsp[0].minor.yy41)->value; + buffer_append_string_buffer(b, rvalue); + + if (NULL != (dc = (data_config *)array_get_element(ctx->all_configs, b->ptr))) { + configparser_push(ctx, dc, 0); + } else { + struct { + comp_key_t comp; + char *comp_key; + size_t len; + } comps[] = { + { COMP_SERVER_SOCKET, CONST_STR_LEN("SERVER[\"socket\"]" ) }, + { COMP_HTTP_URL, CONST_STR_LEN("HTTP[\"url\"]" ) }, + { COMP_HTTP_HOST, CONST_STR_LEN("HTTP[\"host\"]" ) }, + { COMP_HTTP_REFERER, CONST_STR_LEN("HTTP[\"referer\"]" ) }, + { COMP_HTTP_USERAGENT, CONST_STR_LEN("HTTP[\"useragent\"]" ) }, + { COMP_HTTP_COOKIE, CONST_STR_LEN("HTTP[\"cookie\"]" ) }, + { COMP_HTTP_REMOTEIP, CONST_STR_LEN("HTTP[\"remoteip\"]" ) }, + { COMP_UNSET, NULL, 0 }, + }; + size_t i; + + dc = data_config_init(); + + buffer_copy_string_buffer(dc->key, b); + buffer_copy_string_buffer(dc->op, op); + buffer_copy_string_buffer(dc->comp_key, yymsp[-5].minor.yy0); + buffer_append_string_len(dc->comp_key, CONST_STR_LEN("[\"")); + buffer_append_string_buffer(dc->comp_key, yymsp[-3].minor.yy43); + buffer_append_string_len(dc->comp_key, CONST_STR_LEN("\"]")); + dc->cond = yymsp[-1].minor.yy27; + + for (i = 0; comps[i].comp_key; i ++) { + if (buffer_is_equal_string( + dc->comp_key, comps[i].comp_key, comps[i].len)) { + dc->comp = comps[i].comp; + break; + } + } + if (COMP_UNSET == dc->comp) { + fprintf(stderr, "error comp_key %s", dc->comp_key->ptr); + ctx->ok = 0; + } + + switch(yymsp[-1].minor.yy27) { + case CONFIG_COND_NE: + case CONFIG_COND_EQ: + dc->string = buffer_init_buffer(rvalue); + break; + case CONFIG_COND_NOMATCH: + case CONFIG_COND_MATCH: { +#ifdef HAVE_PCRE_H + const char *errptr; + int erroff; + + if (NULL == (dc->regex = + pcre_compile(rvalue->ptr, 0, &errptr, &erroff, NULL))) { + dc->string = buffer_init_string(errptr); + dc->cond = CONFIG_COND_UNSET; + + fprintf(stderr, "parsing regex failed: %s -> %s at offset %d\n", + rvalue->ptr, errptr, erroff); + + ctx->ok = 0; + } else if (NULL == (dc->regex_study = + pcre_study(dc->regex, 0, &errptr)) && + errptr != NULL) { + fprintf(stderr, "studying regex failed: %s -> %s\n", + rvalue->ptr, errptr); + ctx->ok = 0; + } else { + dc->string = buffer_init_buffer(rvalue); + } +#else + fprintf(stderr, "can't handle '$%s[%s] =~ ...' as you compiled without pcre support. \n" + "(perhaps just a missing pcre-devel package ?) \n", + yymsp[-5].minor.yy0->ptr, yymsp[-3].minor.yy43->ptr); + ctx->ok = 0; +#endif + break; + } + + default: + fprintf(stderr, "unknown condition for $%s[%s]\n", + yymsp[-5].minor.yy0->ptr, yymsp[-3].minor.yy43->ptr); + ctx->ok = 0; + break; + } + + configparser_push(ctx, dc, 1); + } + + buffer_free(b); + buffer_free(op); + buffer_free(yymsp[-5].minor.yy0); + yymsp[-5].minor.yy0 = NULL; + buffer_free(yymsp[-3].minor.yy43); + yymsp[-3].minor.yy43 = NULL; + yymsp[0].minor.yy41->free(yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; +} +#line 1269 "configparser.c" + yy_destructor(16,&yymsp[-6].minor); + yy_destructor(18,&yymsp[-4].minor); + yy_destructor(19,&yymsp[-2].minor); + break; + case 32: +#line 496 "./configparser.y" +{ + yygotominor.yy27 = CONFIG_COND_EQ; +} +#line 1279 "configparser.c" + yy_destructor(20,&yymsp[0].minor); + break; + case 33: +#line 499 "./configparser.y" +{ + yygotominor.yy27 = CONFIG_COND_MATCH; +} +#line 1287 "configparser.c" + yy_destructor(21,&yymsp[0].minor); + break; + case 34: +#line 502 "./configparser.y" +{ + yygotominor.yy27 = CONFIG_COND_NE; +} +#line 1295 "configparser.c" + yy_destructor(22,&yymsp[0].minor); + break; + case 35: +#line 505 "./configparser.y" +{ + yygotominor.yy27 = CONFIG_COND_NOMATCH; +} +#line 1303 "configparser.c" + yy_destructor(23,&yymsp[0].minor); + break; + case 36: +#line 509 "./configparser.y" +{ + yygotominor.yy43 = NULL; + if (ctx->ok) { + if (yymsp[0].minor.yy41->type == TYPE_STRING) { + yygotominor.yy43 = buffer_init_buffer(((data_string*)yymsp[0].minor.yy41)->value); + } else if (yymsp[0].minor.yy41->type == TYPE_INTEGER) { + yygotominor.yy43 = buffer_init(); + buffer_copy_long(yygotominor.yy43, ((data_integer *)yymsp[0].minor.yy41)->value); + } else { + fprintf(stderr, "operand must be string"); + ctx->ok = 0; + } + } + yymsp[0].minor.yy41->free(yymsp[0].minor.yy41); + yymsp[0].minor.yy41 = NULL; +} +#line 1324 "configparser.c" + break; + case 37: +#line 526 "./configparser.y" +{ + if (ctx->ok) { + if (0 != config_parse_file(ctx->srv, ctx, yymsp[0].minor.yy43->ptr)) { + ctx->ok = 0; + } + buffer_free(yymsp[0].minor.yy43); + yymsp[0].minor.yy43 = NULL; + } +} +#line 1337 "configparser.c" + yy_destructor(24,&yymsp[-1].minor); + break; + case 38: +#line 536 "./configparser.y" +{ + if (ctx->ok) { + if (0 != config_parse_cmd(ctx->srv, ctx, yymsp[0].minor.yy43->ptr)) { + ctx->ok = 0; + } + buffer_free(yymsp[0].minor.yy43); + yymsp[0].minor.yy43 = NULL; + } +} +#line 1351 "configparser.c" + yy_destructor(25,&yymsp[-1].minor); + 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 */ +){ + configparserARG_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 */ +#line 125 "./configparser.y" + + ctx->ok = 0; + +#line 1385 "configparser.c" + configparserARG_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 */ +){ + configparserARG_FETCH; +#define TOKEN (yyminor.yy0) + configparserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + configparserARG_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 */ + configparserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "configparserAlloc" 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 configparser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + configparserTOKENTYPE yyminor /* The value for the token */ + configparserARG_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); + configparserARG_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/configparser.h b/src/configparser.h new file mode 100644 index 0000000..1610621 --- /dev/null +++ b/src/configparser.h @@ -0,0 +1,25 @@ +#define TK_EOL 1 +#define TK_ASSIGN 2 +#define TK_APPEND 3 +#define TK_LKEY 4 +#define TK_PLUS 5 +#define TK_STRING 6 +#define TK_INTEGER 7 +#define TK_LPARAN 8 +#define TK_RPARAN 9 +#define TK_COMMA 10 +#define TK_ARRAY_ASSIGN 11 +#define TK_GLOBAL 12 +#define TK_LCURLY 13 +#define TK_RCURLY 14 +#define TK_ELSE 15 +#define TK_DOLLAR 16 +#define TK_SRVVARNAME 17 +#define TK_LBRACKET 18 +#define TK_RBRACKET 19 +#define TK_EQ 20 +#define TK_MATCH 21 +#define TK_NE 22 +#define TK_NOMATCH 23 +#define TK_INCLUDE 24 +#define TK_INCLUDE_SHELL 25 diff --git a/src/configparser.y b/src/configparser.y new file mode 100644 index 0000000..ef65061 --- /dev/null +++ b/src/configparser.y @@ -0,0 +1,544 @@ +%token_prefix TK_ +%extra_argument {config_t *ctx} +%name configparser + +%include { +#include <assert.h> +#include <stdio.h> +#include <string.h> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "configfile.h" +#include "buffer.h" +#include "array.h" + +static void configparser_push(config_t *ctx, data_config *dc, int isnew) { + if (isnew) { + dc->context_ndx = ctx->all_configs->used; + assert(dc->context_ndx > ctx->current->context_ndx); + array_insert_unique(ctx->all_configs, (data_unset *)dc); + dc->parent = ctx->current; + array_insert_unique(dc->parent->childs, (data_unset *)dc); + } + array_insert_unique(ctx->configs_stack, (data_unset *)ctx->current); + ctx->current = dc; +} + +static data_config *configparser_pop(config_t *ctx) { + data_config *old = ctx->current; + ctx->current = (data_config *) array_pop(ctx->configs_stack); + return old; +} + +/* return a copied variable */ +static data_unset *configparser_get_variable(config_t *ctx, const buffer *key) { + if (strncmp(key->ptr, "env.", sizeof("env.") - 1) == 0) { + char *env; + + if (NULL != (env = getenv(key->ptr + 4))) { + data_string *ds; + ds = data_string_init(); + buffer_append_string(ds->value, env); + return (data_unset *)ds; + } + + fprintf(stderr, "Undefined env variable: %s\n", key->ptr + 4); + ctx->ok = 0; + + return NULL; + } else { + data_unset *du; + data_config *dc; + +#if 0 + fprintf(stderr, "get var %s\n", key->ptr); +#endif + for (dc = ctx->current; dc; dc = dc->parent) { +#if 0 + fprintf(stderr, "get var on block: %s\n", dc->key->ptr); + array_print(dc->value, 0); +#endif + if (NULL != (du = array_get_element(dc->value, key->ptr))) { + return du->copy(du); + } + } + fprintf(stderr, "Undefined config variable: %s\n", key->ptr); + ctx->ok = 0; + return NULL; + } +} + +/* op1 is to be eat/return by this function, op1->key is not cared + op2 is left untouch, unreferenced + */ +data_unset *configparser_merge_data(data_unset *op1, const data_unset *op2) { + /* type mismatch */ + if (op1->type != op2->type) { + if (op1->type == TYPE_STRING && op2->type == TYPE_INTEGER) { + data_string *ds = (data_string *)op1; + buffer_append_long(ds->value, ((data_integer*)op2)->value); + return op1; + } else if (op1->type == TYPE_INTEGER && op2->type == TYPE_STRING) { + data_string *ds = data_string_init(); + buffer_append_long(ds->value, ((data_integer*)op1)->value); + buffer_append_string_buffer(ds->value, ((data_string*)op2)->value); + op1->free(op1); + return (data_unset *)ds; + } else { + fprintf(stderr, "data type mismatch, cannot be merge\n"); + op1->free(op1); + return NULL; + } + } + + switch (op1->type) { + case TYPE_STRING: + buffer_append_string_buffer(((data_string *)op1)->value, ((data_string *)op2)->value); + break; + case TYPE_INTEGER: + ((data_integer *)op1)->value += ((data_integer *)op2)->value; + break; + case TYPE_ARRAY: { + array *dst = ((data_array *)op1)->value; + array *src = ((data_array *)op2)->value; + data_unset *du; + size_t i; + + for (i = 0; i < src->used; i ++) { + du = (data_unset *)src->data[i]; + if (du) { + array_insert_unique(dst, du->copy(du)); + } + } + break; + default: + assert(0); + break; + } + } + return op1; +} + +} + +%parse_failure { + ctx->ok = 0; +} + +input ::= metalines. +metalines ::= metalines metaline. +metalines ::= . +metaline ::= varline. +metaline ::= global. +metaline ::= condlines(A) EOL. { A = NULL; } +metaline ::= include. +metaline ::= include_shell. +metaline ::= EOL. + +%type value {data_unset *} +%type expression {data_unset *} +%type aelement {data_unset *} +%type condline {data_config *} +%type condlines {data_config *} +%type aelements {array *} +%type array {array *} +%type key {buffer *} +%type stringop {buffer *} + +%type cond {config_cond_t } + +%destructor value { $$->free($$); } +%destructor expression { $$->free($$); } +%destructor aelement { $$->free($$); } +%destructor aelements { array_free($$); } +%destructor array { array_free($$); } +%destructor key { buffer_free($$); } +%destructor stringop { buffer_free($$); } + +%token_type {buffer *} +%token_destructor { buffer_free($$); } + +varline ::= key(A) ASSIGN expression(B). { + buffer_copy_string_buffer(B->key, A); + if (NULL == array_get_element(ctx->current->value, B->key->ptr)) { + array_insert_unique(ctx->current->value, B); + B = NULL; + } else { + fprintf(stderr, "Duplicate config variable in conditional %d %s: %s\n", + ctx->current->context_ndx, + ctx->current->key->ptr, B->key->ptr); + ctx->ok = 0; + B->free(B); + B = NULL; + } + buffer_free(A); + A = NULL; +} + +varline ::= key(A) APPEND expression(B). { + array *vars = ctx->current->value; + data_unset *du; + + if (NULL != (du = array_get_element(vars, A->ptr))) { + /* exists in current block */ + du = configparser_merge_data(du, B); + if (NULL == du) { + ctx->ok = 0; + } + else { + buffer_copy_string_buffer(du->key, A); + array_replace(vars, du); + } + } else if (NULL != (du = configparser_get_variable(ctx, A))) { + du = configparser_merge_data(du, B); + if (NULL == du) { + ctx->ok = 0; + } + else { + buffer_copy_string_buffer(du->key, A); + array_insert_unique(ctx->current->value, du); + } + } else { + fprintf(stderr, "Undefined config variable in conditional %d %s: %s\n", + ctx->current->context_ndx, + ctx->current->key->ptr, A->ptr); + ctx->ok = 0; + } + buffer_free(A); + A = NULL; + B->free(B); + B = NULL; +} + +key(A) ::= LKEY(B). { + if (strchr(B->ptr, '.') == NULL) { + A = buffer_init_string("var."); + buffer_append_string_buffer(A, B); + buffer_free(B); + B = NULL; + } else { + A = B; + B = NULL; + } +} + +expression(A) ::= expression(B) PLUS value(C). { + A = configparser_merge_data(B, C); + if (NULL == A) { + ctx->ok = 0; + } + B = NULL; + C->free(C); + C = NULL; +} + +expression(A) ::= value(B). { + A = B; + B = NULL; +} + +value(A) ::= key(B). { + A = configparser_get_variable(ctx, B); + if (!A) { + /* make a dummy so it won't crash */ + A = (data_unset *)data_string_init(); + } + buffer_free(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); + B = NULL; +} + +value(A) ::= INTEGER(B). { + A = (data_unset *)data_integer_init(); + ((data_integer *)(A))->value = strtol(B->ptr, NULL, 10); + buffer_free(B); + B = NULL; +} +value(A) ::= array(B). { + A = (data_unset *)data_array_init(); + array_free(((data_array *)(A))->value); + ((data_array *)(A))->value = B; + B = NULL; +} +array(A) ::= LPARAN aelements(B) RPARAN. { + A = B; + B = NULL; +} + +aelements(A) ::= aelements(C) COMMA aelement(B). { + if (buffer_is_empty(B->key) || + NULL == array_get_element(C, B->key->ptr)) { + array_insert_unique(C, B); + B = NULL; + } else { + fprintf(stderr, "Duplicate array-key: %s\n", + B->key->ptr); + ctx->ok = 0; + B->free(B); + B = NULL; + } + + A = C; + C = NULL; +} + +aelements(A) ::= aelements(C) COMMA. { + A = C; + C = NULL; +} + +aelements(A) ::= aelement(B). { + A = array_init(); + array_insert_unique(A, B); + B = NULL; +} + +aelement(A) ::= expression(B). { + A = B; + B = NULL; +} +aelement(A) ::= stringop(B) ARRAY_ASSIGN expression(C). { + buffer_copy_string_buffer(C->key, B); + buffer_free(B); + B = NULL; + + A = C; + C = NULL; +} + +eols ::= EOL. +eols ::= . + +globalstart ::= GLOBAL. { + data_config *dc; + dc = (data_config *)array_get_element(ctx->srv->config_context, "global"); + assert(dc); + configparser_push(ctx, dc, 0); +} + +global(A) ::= globalstart LCURLY metalines RCURLY. { + data_config *cur; + + cur = ctx->current; + configparser_pop(ctx); + + assert(cur && ctx->current); + + A = cur; +} + +condlines(A) ::= condlines(B) eols ELSE condline(C). { + assert(B->context_ndx < C->context_ndx); + C->prev = B; + B->next = C; + A = C; + B = NULL; + C = NULL; +} + +condlines(A) ::= condline(B). { + A = B; + B = NULL; +} + +condline(A) ::= context LCURLY metalines RCURLY. { + data_config *cur; + + cur = ctx->current; + configparser_pop(ctx); + + assert(cur && ctx->current); + + A = cur; +} + +context ::= DOLLAR SRVVARNAME(B) LBRACKET stringop(C) RBRACKET cond(E) expression(D). { + data_config *dc; + buffer *b, *rvalue, *op; + + if (ctx->ok && D->type != TYPE_STRING) { + fprintf(stderr, "rvalue must be string"); + ctx->ok = 0; + } + + switch(E) { + case CONFIG_COND_NE: + op = buffer_init_string("!="); + break; + case CONFIG_COND_EQ: + op = buffer_init_string("=="); + break; + case CONFIG_COND_NOMATCH: + op = buffer_init_string("!~"); + break; + case CONFIG_COND_MATCH: + op = buffer_init_string("=~"); + break; + default: + assert(0); + return; + } + + b = buffer_init(); + buffer_copy_string_buffer(b, ctx->current->key); + buffer_append_string(b, "/"); + buffer_append_string_buffer(b, B); + buffer_append_string_buffer(b, C); + buffer_append_string_buffer(b, op); + rvalue = ((data_string*)D)->value; + buffer_append_string_buffer(b, rvalue); + + if (NULL != (dc = (data_config *)array_get_element(ctx->all_configs, b->ptr))) { + configparser_push(ctx, dc, 0); + } else { + struct { + comp_key_t comp; + char *comp_key; + size_t len; + } comps[] = { + { COMP_SERVER_SOCKET, CONST_STR_LEN("SERVER[\"socket\"]" ) }, + { COMP_HTTP_URL, CONST_STR_LEN("HTTP[\"url\"]" ) }, + { COMP_HTTP_HOST, CONST_STR_LEN("HTTP[\"host\"]" ) }, + { COMP_HTTP_REFERER, CONST_STR_LEN("HTTP[\"referer\"]" ) }, + { COMP_HTTP_USERAGENT, CONST_STR_LEN("HTTP[\"useragent\"]" ) }, + { COMP_HTTP_COOKIE, CONST_STR_LEN("HTTP[\"cookie\"]" ) }, + { COMP_HTTP_REMOTEIP, CONST_STR_LEN("HTTP[\"remoteip\"]" ) }, + { COMP_UNSET, NULL, 0 }, + }; + size_t i; + + dc = data_config_init(); + + buffer_copy_string_buffer(dc->key, b); + buffer_copy_string_buffer(dc->op, op); + buffer_copy_string_buffer(dc->comp_key, B); + buffer_append_string_len(dc->comp_key, CONST_STR_LEN("[\"")); + buffer_append_string_buffer(dc->comp_key, C); + buffer_append_string_len(dc->comp_key, CONST_STR_LEN("\"]")); + dc->cond = E; + + for (i = 0; comps[i].comp_key; i ++) { + if (buffer_is_equal_string( + dc->comp_key, comps[i].comp_key, comps[i].len)) { + dc->comp = comps[i].comp; + break; + } + } + if (COMP_UNSET == dc->comp) { + fprintf(stderr, "error comp_key %s", dc->comp_key->ptr); + ctx->ok = 0; + } + + switch(E) { + case CONFIG_COND_NE: + case CONFIG_COND_EQ: + dc->string = buffer_init_buffer(rvalue); + break; + case CONFIG_COND_NOMATCH: + case CONFIG_COND_MATCH: { +#ifdef HAVE_PCRE_H + const char *errptr; + int erroff; + + if (NULL == (dc->regex = + pcre_compile(rvalue->ptr, 0, &errptr, &erroff, NULL))) { + dc->string = buffer_init_string(errptr); + dc->cond = CONFIG_COND_UNSET; + + fprintf(stderr, "parsing regex failed: %s -> %s at offset %d\n", + rvalue->ptr, errptr, erroff); + + ctx->ok = 0; + } else if (NULL == (dc->regex_study = + pcre_study(dc->regex, 0, &errptr)) && + errptr != NULL) { + fprintf(stderr, "studying regex failed: %s -> %s\n", + rvalue->ptr, errptr); + ctx->ok = 0; + } else { + dc->string = buffer_init_buffer(rvalue); + } +#else + fprintf(stderr, "can't handle '$%s[%s] =~ ...' as you compiled without pcre support. \n" + "(perhaps just a missing pcre-devel package ?) \n", + B->ptr, C->ptr); + ctx->ok = 0; +#endif + break; + } + + default: + fprintf(stderr, "unknown condition for $%s[%s]\n", + B->ptr, C->ptr); + ctx->ok = 0; + break; + } + + configparser_push(ctx, dc, 1); + } + + buffer_free(b); + buffer_free(op); + buffer_free(B); + B = NULL; + buffer_free(C); + C = NULL; + D->free(D); + D = NULL; +} +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; +} + +stringop(A) ::= expression(B). { + A = NULL; + if (ctx->ok) { + if (B->type == TYPE_STRING) { + A = buffer_init_buffer(((data_string*)B)->value); + } else if (B->type == TYPE_INTEGER) { + A = buffer_init(); + buffer_copy_long(A, ((data_integer *)B)->value); + } else { + fprintf(stderr, "operand must be string"); + ctx->ok = 0; + } + } + B->free(B); + B = NULL; +} + +include ::= INCLUDE stringop(A). { + if (ctx->ok) { + if (0 != config_parse_file(ctx->srv, ctx, A->ptr)) { + ctx->ok = 0; + } + buffer_free(A); + A = NULL; + } +} + +include_shell ::= INCLUDE_SHELL stringop(A). { + if (ctx->ok) { + if (0 != config_parse_cmd(ctx->srv, ctx, A->ptr)) { + ctx->ok = 0; + } + buffer_free(A); + A = NULL; + } +} diff --git a/src/connections-glue.c b/src/connections-glue.c new file mode 100644 index 0000000..ac6d267 --- /dev/null +++ b/src/connections-glue.c @@ -0,0 +1,44 @@ +#include "base.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"; + } +} + +int connection_set_state(server *srv, connection *con, connection_state_t state) { + UNUSED(srv); + + con->state = state; + + return 0; +} + diff --git a/src/connections.c b/src/connections.c new file mode 100644 index 0000000..e4db410 --- /dev/null +++ b/src/connections.c @@ -0,0 +1,1708 @@ +#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 "stat_cache.h" +#include "joblist.h" + +#include "plugin.h" + +#include "inet_ntop_cache.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" + +typedef struct { + PLUGIN_DATA; +} plugin_data; + +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; +} + +#if 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"); + } +} +#endif + +static int connection_handle_read(server *srv, connection *con) { + int len; + buffer *b; + int toread; +#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 { + if (ioctl(con->fd, FIONREAD, &toread)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "unexpected end-of-file:", + con->fd); + return -1; + } + buffer_prepare_copy(b, toread); + + len = read(con->fd, b->ptr, b->size - 1); + } +#elif defined(__WIN32) + len = recv(con->fd, b->ptr, b->size - 1, 0); +#else + if (ioctl(con->fd, FIONREAD, &toread)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "unexpected end-of-file:", + con->fd); + return -1; + } + buffer_prepare_copy(b, toread); + + 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, ssl_err; + + switch ((r = SSL_get_error(con->ssl, len))) { + case SSL_ERROR_WANT_READ: + return 0; + case SSL_ERROR_SYSCALL: + /** + * man SSL_get_error() + * + * SSL_ERROR_SYSCALL + * Some I/O error occurred. The OpenSSL error queue may contain more + * information on the error. If the error queue is empty (i.e. + * ERR_get_error() returns 0), ret can be used to find out more about + * the error: If ret == 0, an EOF was observed that violates the + * protocol. If ret == -1, the underlying BIO reported an I/O error + * (for socket I/O on Unix systems, consult errno for details). + * + */ + while((ssl_err = ERR_get_error())) { + /* get all errors from the error-queue */ + log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", + r, ERR_error_string(ssl_err, NULL)); + } + + switch(errno) { + default: + log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:", + len, r, errno, + strerror(errno)); + break; + } + + break; + case SSL_ERROR_ZERO_RETURN: + /* clean shutdown on the remote side */ + + if (r == 0) { + /* FIXME: later */ + } + + /* fall thourgh */ + default: + while((ssl_err = ERR_get_error())) { + /* get all errors from the error-queue */ + log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", + r, ERR_error_string(ssl_err, 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 */ + + /* pipelining */ + + return -2; + } 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) { + if (con->mode == DIRECT) { + /* static files */ + switch(con->request.http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_POST: + case HTTP_METHOD_HEAD: + case HTTP_METHOD_PUT: + case HTTP_METHOD_MKCOL: + case HTTP_METHOD_DELETE: + case HTTP_METHOD_COPY: + case HTTP_METHOD_MOVE: + case HTTP_METHOD_PROPFIND: + case HTTP_METHOD_PROPPATCH: + break; + case HTTP_METHOD_OPTIONS: + /* + * 400 is coming from the request-parser BEFORE uri.path is set + * 403 is from the response handler when noone else catched it + * + * */ + if (con->uri.path->used && + con->uri.path->ptr[0] != '*') { + response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST")); + + con->http_status = 200; + con->file_finished = 1; + + chunkqueue_reset(con->write_queue); + } + break; + default: + switch(con->http_status) { + case 400: /* bad request */ + case 414: /* overload request header */ + case 505: /* unknown protocol */ + case 207: /* this was webdav */ + break; + default: + con->http_status = 501; + break; + } + break; + } + } + + if (con->http_status == 0) { + con->http_status = 403; + } + + 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: + if (con->mode != DIRECT) break; + + con->file_finished = 0; + + buffer_reset(con->physical.path); + + /* try to send static errorfile */ + if (!buffer_is_empty(con->conf.errorfile_prefix)) { + stat_cache_entry *sce = NULL; + + buffer_copy_string_buffer(con->physical.path, con->conf.errorfile_prefix); + buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status)); + + if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + con->file_finished = 1; + + http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size); + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); + } + } + + if (!con->file_finished) { + buffer *b; + + buffer_reset(con->physical.path); + + con->file_finished = 1; + 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")); + } + /* fall through */ + case 207: + case 200: /* class: header + body */ + case 301: + case 302: + break; + + case 206: /* write_queue is already prepared */ + con->file_finished = 1; + + break; + case 205: /* class: header only */ + 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); + + con->file_finished = 1; + break; + } + + + if (con->file_finished) { + /* we have all the content and chunked encoding is not used, set a content-length */ + + if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) && + (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) { + buffer_copy_off_t(srv->tmp_buf, chunkqueue_length(con->write_queue)); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf)); + } + } else { + /* 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 (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 */ + } + } + } + + if (con->request.http_method == HTTP_METHOD_HEAD) { + chunkqueue_reset(con->write_queue); + } + + http_response_write_header(srv, con); + + 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; + con->loops_per_request = 0; + +#define CLEAN(x) \ + con->x = buffer_init(); + + CLEAN(request.uri); + CLEAN(request.request_line); + CLEAN(request.request); + CLEAN(request.pathinfo); + + 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.basedir); + CLEAN(physical.rel_path); + CLEAN(physical.etag); + CLEAN(parse_request); + + CLEAN(authed_user); + CLEAN(server_name); + CLEAN(error_handler); + CLEAN(dst_addr_buf); + +#undef CLEAN + con->write_queue = chunkqueue_init(); + con->read_queue = chunkqueue_init(); + con->request_content_queue = chunkqueue_init(); + chunkqueue_set_tempdirs(con->request_content_queue, srv->srvconf.upload_tempdirs); + + con->request.headers = array_init(); + con->response.headers = array_init(); + con->environment = array_init(); + + /* init plugin specific connection structures */ + + con->plugin_ctx = calloc(1, (srv->plugins.used + 1) * sizeof(void *)); + + con->cond_cache = calloc(srv->config_context->used, sizeof(cond_cache_t)); + 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); + chunkqueue_free(con->request_content_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.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.basedir); + CLEAN(physical.etag); + CLEAN(physical.rel_path); + CLEAN(parse_request); + + CLEAN(authed_user); + CLEAN(server_name); + CLEAN(error_handler); + CLEAN(dst_addr_buf); +#undef CLEAN + free(con->plugin_ctx); + free(con->cond_cache); + + 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->loops_per_request = 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.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.basedir); + 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); + chunkqueue_reset(con->request_content_queue); + + /* the plugins should cleanup themself */ + for (i = 0; i < srv->plugins.used; i++) { + plugin *p = ((plugin **)(srv->plugins.ptr))[i]; + plugin_data *pd = p->data; + + if (!pd) continue; + + if (con->plugin_ctx[pd->id] != NULL) { + log_error_write(srv, __FILE__, __LINE__, "sb", "missing cleanup in", p->name); + } + + con->plugin_ctx[pd->id] = NULL; + } + +#if COND_RESULT_UNSET + for (i = srv->config_context->used - 1; i >= 0; i --) { + con->cond_cache[i].result = COND_RESULT_UNSET; + con->cond_cache[i].patterncount = 0; + } +#else + memset(con->cond_cache, 0, sizeof(cond_cache_t) * srv->config_context->used); +#endif + + 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; +} +/** + * handle all header and content read + * + * we get called by the state-engine and by the fdevent-handler + */ +int connection_handle_read_state(server *srv, connection *con) { + int ostate = con->state; + char *h_term = NULL; + chunk *c; + chunkqueue *cq = con->read_queue; + chunkqueue *dst_cq = con->request_content_queue; + + if (con->is_readable) { + con->read_idle_ts = srv->cur_ts; + + switch(connection_handle_read(srv, con)) { + case -1: + return -1; + case -2: + /* remote side closed the connection + * if we still have content, handle it, if not leave here */ + + if (cq->first == cq->last && + cq->first->mem->used == 0) { + + /* conn-closed, leave here */ + connection_set_state(srv, con, CON_STATE_ERROR); + } + default: + break; + } + } + + /* the last chunk might be empty */ + for (c = cq->first; c;) { + if (cq->first == c && c->mem->used == 0) { + /* the first node is empty */ + /* ... and it is empty, move it to unused */ + + cq->first = c->next; + if (cq->first == NULL) cq->last = NULL; + + c->next = cq->unused; + cq->unused = c; + cq->unused_chunks++; + + c = cq->first; + } else if (c->next && c->next->mem->used == 0) { + chunk *fc; + /* next node is the last one */ + /* ... and it is empty, move it to unused */ + + fc = c->next; + c->next = fc->next; + + fc->next = cq->unused; + cq->unused = fc; + cq->unused_chunks++; + + /* the last node was empty */ + if (c->next == NULL) { + cq->last = c; + } + + c = c->next; + } else { + c = c->next; + } + } + + /* 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->mem->ptr + c->offset; + b.used = c->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->mem->ptr + c->offset, c->mem->used - c->offset - 1); + c->offset = c->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->mem->ptr + c->offset; + b.used = c->mem->used - c->offset; + + if (con->request.request->used - 1 > 3 && + c->mem->used > 1 && + s[l-2] == '\r' && + s[l-1] == '\n' && + s[l-0] == '\r' && + c->mem->ptr[0] == '\n') { + buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 1); + c->offset += 1; + + h_term = con->request.request->ptr; + } else if (con->request.request->used - 1 > 2 && + c->mem->used > 2 && + s[l-1] == '\r' && + s[l-0] == '\n' && + c->mem->ptr[0] == '\r' && + c->mem->ptr[1] == '\n') { + buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 2); + c->offset += 2; + + h_term = con->request.request->ptr; + } else if (con->request.request->used - 1 > 1 && + c->mem->used > 3 && + s[l-0] == '\r' && + c->mem->ptr[0] == '\n' && + c->mem->ptr[1] == '\r' && + c->mem->ptr[2] == '\n') { + buffer_append_string_len(con->request.request, c->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->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->mem->ptr + c->offset, c->mem->used - c->offset - 1); + c->offset = c->mem->used - 1; + } + } + + /* con->request.request is setup up */ + if (h_term) { + connection_set_state(srv, con, CON_STATE_REQUEST_END); + } else if (con->request.request->used > 64 * 1024) { + log_error_write(srv, __FILE__, __LINE__, "s", "oversized request-header -> sending Status 414"); + + con->http_status = 414; /* Request-URI too large */ + con->keep_alive = 0; + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + } + break; + case CON_STATE_READ_POST: + for (c = cq->first; c && (dst_cq->bytes_in != (off_t)con->request.content_length); c = c->next) { + off_t weWant, weHave, toRead; + + weWant = con->request.content_length - dst_cq->bytes_in; + + assert(c->mem->used); + + weHave = c->mem->used - c->offset - 1; + + toRead = weHave > weWant ? weWant : weHave; + + /* the new way, copy everything into a chunkqueue whcih might use tempfiles */ + if (con->request.content_length > 64 * 1024) { + chunk *dst_c = NULL; + /* copy everything to max 1Mb sized tempfiles */ + + /* + * if the last chunk is + * - smaller than 1Mb (size < 1Mb) + * - not read yet (offset == 0) + * -> append to it + * otherwise + * -> create a new chunk + * + * */ + + if (dst_cq->last && + dst_cq->last->type == FILE_CHUNK && + dst_cq->last->file.is_temp && + dst_cq->last->offset == 0) { + /* ok, take the last chunk for our job */ + + if (dst_cq->last->file.length < 1 * 1024 * 1024) { + dst_c = dst_cq->last; + + if (dst_c->file.fd == -1) { + /* this should not happen as we cache the fd, but you never know */ + dst_c->file.fd = open(dst_c->file.name->ptr, O_WRONLY | O_APPEND); + } + } else { + /* the chunk is too large now, close it */ + dst_c = dst_cq->last; + + if (dst_c->file.fd != -1) { + close(dst_c->file.fd); + dst_c->file.fd = -1; + } + dst_c = chunkqueue_get_append_tempfile(dst_cq); + } + } else { + dst_c = chunkqueue_get_append_tempfile(dst_cq); + } + + /* we have a chunk, let's write to it */ + + if (dst_c->file.fd == -1) { + /* we don't have file to write to, + * EACCES might be one reason. + * + * Instead of sending 500 we send 413 and say the request is too large + * */ + + log_error_write(srv, __FILE__, __LINE__, "sbs", + "denying upload as opening to temp-file for upload failed:", + dst_c->file.name, strerror(errno)); + + con->http_status = 413; /* Request-Entity too large */ + con->keep_alive = 0; + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + + break; + } + + if (toRead != write(dst_c->file.fd, c->mem->ptr + c->offset, toRead)) { + /* write failed for some reason ... disk full ? */ + log_error_write(srv, __FILE__, __LINE__, "sbs", + "denying upload as writing to file failed:", + dst_c->file.name, strerror(errno)); + + con->http_status = 413; /* Request-Entity too large */ + con->keep_alive = 0; + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + + close(dst_c->file.fd); + dst_c->file.fd = -1; + + break; + } + + dst_c->file.length += toRead; + + if (dst_cq->bytes_in + toRead == (off_t)con->request.content_length) { + /* we read everything, close the chunk */ + close(dst_c->file.fd); + dst_c->file.fd = -1; + } + } else { + buffer *b; + + b = chunkqueue_get_append_buffer(dst_cq); + buffer_copy_string_len(b, c->mem->ptr + c->offset, toRead); + } + + c->offset += toRead; + dst_cq->bytes_in += toRead; + } + + /* Content is ready */ + if (dst_cq->bytes_in == (off_t)con->request.content_length) { + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + } + + break; + } + + chunkqueue_remove_finished_chunks(cq); + + 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) { + /* 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__, "ssd", "accept failed:", strerror(errno), errno); + } + return NULL; + } else { + connection *con; + + srv->cur_fds++; + + /* 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; + buffer_copy_string(con->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_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; + } + + SSL_set_accept_state(con->ssl); + con->conf.is_ssl=1; + + 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_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++; + con->loops_per_request = 0; + + 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", + "Warning: Either the error-handler returned status 404 or the error-handler itself was not found:", con->request.uri); + log_error_write(srv, __FILE__, __LINE__, "sd", + "returning the original status", con->error_handler_saved_status); + log_error_write(srv, __FILE__, __LINE__, "s", + "If this is a rails app: check your production.log"); + con->http_status = con->error_handler_saved_status; + } + } else if (con->in_error_handler) { + /* error-handler is back and has generated content */ + /* if Status: was set, take it otherwise use 200 */ + } + + 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)); + } + + if (-1 == connection_handle_write_prepare(srv, con)) { + connection_set_state(srv, con, CON_STATE_ERROR); + + break; + } + + 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 0000000..1fcfc36 --- /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 0000000..cdad7bc --- /dev/null +++ b/src/crc32.c @@ -0,0 +1,82 @@ +#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, +}; + + +uint32_t generate_crc32c(char *buffer, size_t length) { + size_t i; + uint32_t crc32 = ~0L; + + for (i = 0; i < length; i++){ + CRC32C(crc32, (unsigned char)buffer[i]); + } + return ~crc32; +} + diff --git a/src/crc32.h b/src/crc32.h new file mode 100644 index 0000000..d671c53 --- /dev/null +++ b/src/crc32.h @@ -0,0 +1,18 @@ +#ifndef __crc32cr_table_h__ +#define __crc32cr_table_h__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> + +#if defined HAVE_STDINT_H +#include <stdint.h> +#elif defined HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +uint32_t generate_crc32c(char *string, size_t length); + +#endif diff --git a/src/data_array.c b/src/data_array.c new file mode 100644 index 0000000..9dfa6fd --- /dev/null +++ b/src/data_array.c @@ -0,0 +1,65 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" + +static data_unset *data_array_copy(const data_unset *s) { + data_array *src = (data_array *)s; + data_array *ds = data_array_init(); + + buffer_copy_string_buffer(ds->key, src->key); + array_free(ds->value); + ds->value = array_init_array(src->value); + ds->is_index_key = src->is_index_key; + return (data_unset *)ds; +} + +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(const data_unset *d, int depth) { + data_array *ds = (data_array *)d; + + array_print(ds->value, depth); +} + +data_array *data_array_init(void) { + data_array *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->value = array_init(); + + ds->copy = data_array_copy; + 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 0000000..8b3735e --- /dev/null +++ b/src/data_config.c @@ -0,0 +1,138 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" + +static data_unset *data_config_copy(const data_unset *s) { + data_config *src = (data_config *)s; + data_config *ds = data_config_init(); + + buffer_copy_string_buffer(ds->key, src->key); + buffer_copy_string_buffer(ds->comp_key, src->comp_key); + array_free(ds->value); + ds->value = array_init_array(src->value); + return (data_unset *)ds; +} + +static void data_config_free(data_unset *d) { + data_config *ds = (data_config *)d; + + buffer_free(ds->key); + buffer_free(ds->op); + buffer_free(ds->comp_key); + + array_free(ds->value); + array_free(ds->childs); + + if (ds->string) buffer_free(ds->string); +#ifdef HAVE_PCRE_H + if (ds->regex) pcre_free(ds->regex); + if (ds->regex_study) pcre_free(ds->regex_study); +#endif + + 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(const data_unset *d, int depth) { + data_config *ds = (data_config *)d; + array *a = (array *)ds->value; + size_t i; + size_t maxlen; + + if (0 == ds->context_ndx) { + fprintf(stderr, "config {\n"); + } + else { + fprintf(stderr, "$%s %s \"%s\" {\n", + ds->comp_key->ptr, ds->op->ptr, ds->string->ptr); + array_print_indent(depth + 1); + fprintf(stderr, "# block %d\n", ds->context_ndx); + } + depth ++; + + maxlen = array_get_max_key_length(a); + for (i = 0; i < a->used; i ++) { + data_unset *du = a->data[i]; + size_t len = strlen(du->key->ptr); + size_t j; + + array_print_indent(depth); + fprintf(stderr, "%s", du->key->ptr); + for (j = maxlen - len; j > 0; j --) { + fprintf(stderr, " "); + } + fprintf(stderr, " = "); + du->print(du, depth); + fprintf(stderr, "\n"); + } + + if (ds->childs) { + fprintf(stderr, "\n"); + for (i = 0; i < ds->childs->used; i ++) { + data_unset *du = ds->childs->data[i]; + + /* only the 1st block of chaining */ + if (NULL == ((data_config *)du)->prev) { + fprintf(stderr, "\n"); + array_print_indent(depth); + du->print(du, depth); + fprintf(stderr, "\n"); + } + } + } + + depth --; + array_print_indent(depth); + fprintf(stderr, "}"); + if (0 != ds->context_ndx) { + fprintf(stderr, " # end of $%s %s \"%s\"", + ds->comp_key->ptr, ds->op->ptr, ds->string->ptr); + } + + if (ds->next) { + fprintf(stderr, "\n"); + array_print_indent(depth); + fprintf(stderr, "else "); + ds->next->print((data_unset *)ds->next, depth); + } +} + +data_config *data_config_init(void) { + data_config *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->op = buffer_init(); + ds->comp_key = buffer_init(); + ds->value = array_init(); + ds->childs = array_init(); + ds->childs->is_weakref = 1; + + ds->copy = data_config_copy; + 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 0000000..ca51f67 --- /dev/null +++ b/src/data_count.c @@ -0,0 +1,68 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" + +static data_unset *data_count_copy(const data_unset *s) { + data_count *src = (data_count *)s; + data_count *ds = data_count_init(); + + buffer_copy_string_buffer(ds->key, src->key); + ds->count = src->count; + ds->is_index_key = src->is_index_key; + return (data_unset *)ds; +} + +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(const data_unset *d, int depth) { + data_count *ds = (data_count *)d; + UNUSED(depth); + + fprintf(stderr, "count(%d)", ds->count); +} + + +data_count *data_count_init(void) { + data_count *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->count = 1; + + ds->copy = data_count_copy; + 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 0000000..714b290 --- /dev/null +++ b/src/data_fastcgi.c @@ -0,0 +1,69 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" +#include "fastcgi.h" + +static data_unset *data_fastcgi_copy(const data_unset *s) { + data_fastcgi *src = (data_fastcgi *)s; + data_fastcgi *ds = data_fastcgi_init(); + + buffer_copy_string_buffer(ds->key, src->key); + buffer_copy_string_buffer(ds->host, src->host); + ds->is_index_key = src->is_index_key; + return (data_unset *)ds; +} + +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(const data_unset *d, int depth) { + data_fastcgi *ds = (data_fastcgi *)d; + UNUSED(depth); + + fprintf(stderr, "fastcgi(%s)", 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->is_disabled = 0; + + ds->copy = data_fastcgi_copy; + 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 0000000..96d1d0a --- /dev/null +++ b/src/data_integer.c @@ -0,0 +1,65 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "array.h" + +static data_unset *data_integer_copy(const data_unset *s) { + data_integer *src = (data_integer *)s; + data_integer *ds = data_integer_init(); + + buffer_copy_string_buffer(ds->key, src->key); + ds->is_index_key = src->is_index_key; + ds->value = src->value; + return (data_unset *)ds; +} + +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(const data_unset *d, int depth) { + data_integer *ds = (data_integer *)d; + UNUSED(depth); + + fprintf(stderr, "%d", ds->value); +} + + +data_integer *data_integer_init(void) { + data_integer *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->value = 0; + + ds->copy = data_integer_copy; + 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 0000000..d9325da --- /dev/null +++ b/src/data_string.c @@ -0,0 +1,104 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "array.h" + +static data_unset *data_string_copy(const data_unset *s) { + data_string *src = (data_string *)s; + data_string *ds = data_string_init(); + + buffer_copy_string_buffer(ds->key, src->key); + buffer_copy_string_buffer(ds->value, src->value); + ds->is_index_key = src->is_index_key; + return (data_unset *)ds; +} + +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(const data_unset *d, int depth) { + data_string *ds = (data_string *)d; + UNUSED(depth); + + fprintf(stderr, "\"%s\"", 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->copy = data_string_copy; + 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 0000000..89dfb9c --- /dev/null +++ b/src/etag.c @@ -0,0 +1,32 @@ +#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, CONST_STR_LEN("-")); + buffer_append_off_t(etag, st->st_size); + buffer_append_string_len(etag, CONST_STR_LEN("-")); + 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_string_len(mut, CONST_STR_LEN("\"")); + buffer_append_long(mut, h); + buffer_append_string_len(mut, CONST_STR_LEN("\"")); + + return 0; +} diff --git a/src/etag.h b/src/etag.h new file mode 100644 index 0000000..53fae00 --- /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 0000000..15f1dea --- /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 0000000..fdf834f --- /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) && (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 0000000..0bc05ca --- /dev/null +++ b/src/fdevent.h @@ -0,0 +1,222 @@ +#ifndef _FDEVENT_H_ +#define _FDEVENT_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#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 +# ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +# endif +#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 { + 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; + + 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 0000000..b955726 --- /dev/null +++ b/src/fdevent_freebsd_kqueue.c @@ -0,0 +1,207 @@ +#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) { + switch(errno) { + case EINTR: + /* we got interrupted, perhaps just a SIGCHLD of a CGI script */ + return 0; + default: + fprintf(stderr, "%s.%d: kqueue failed polling: %s\n", + __FILE__, __LINE__, strerror(errno)); + break; + } + } + + 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) { + 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); + + ev->kq_fd = -1; + + ev->kq_results = calloc(ev->maxfds, sizeof(*ev->kq_results)); + ev->kq_bevents = bitset_init(ev->maxfds); + + /* check that kqueue works */ + + 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; + } + + close(ev->kq_fd); + ev->kq_fd = -1; + + 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 0000000..dcefff8 --- /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 %zu\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: %zu %zu\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 0000000..31caabd --- /dev/null +++ b/src/fdevent_linux_sysepoll.c @@ -0,0 +1,160 @@ +#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; + + /** + * + * with EPOLLET we don't get a FDEVENT_HUP + * if the close is delay after everything has + * sent. + * + */ + + 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 0000000..7d8017a --- /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 %zd\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: %zd >= %zd\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 0000000..3eb10f3 --- /dev/null +++ b/src/fdevent_select.c @@ -0,0 +1,131 @@ +#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 <assert.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); + + /* we should be protected by max-fds, but you never know */ + assert(fd < FD_SETSIZE); + + 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 0000000..91238b0 --- /dev/null +++ b/src/fdevent_solaris_devpoll.c @@ -0,0 +1,158 @@ +#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_reset(fdevents *ev) { + /* a forked process does only inherit the filedescriptor, + * but every operation on the device will lead to a EACCES */ + 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; +} +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(reset); + + 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; + } + + /* we just wanted to check if it works */ + close(ev->devpoll_fd); + + ev->devpoll_fd = -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/http-header-glue.c b/src/http-header-glue.c new file mode 100644 index 0000000..4771a5e --- /dev/null +++ b/src/http-header-glue.c @@ -0,0 +1,319 @@ +#define _GNU_SOURCE + +#include <string.h> +#include <errno.h> +#include <time.h> + +#include "base.h" +#include "array.h" +#include "buffer.h" +#include "log.h" +#include "etag.h" + +/* + * 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_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__, + "SdSS", "NOTICE: gethostbyaddr failed: ", + 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; + con->file_finished = 1; + + buffer_free(o); + + return 0; +} + +buffer * strftime_cache_get(server *srv, time_t last_mod) { + struct tm *tm; + size_t i; + + for (i = 0; i < FILE_CACHE_MAX; i++) { + /* found cache-entry */ + if (srv->mtime_cache[i].mtime == last_mod) return srv->mtime_cache[i].str; + + /* found empty slot */ + if (srv->mtime_cache[i].mtime == 0) 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++; + + return srv->mtime_cache[i].str; +} + + +int http_response_handle_cachable(server *srv, connection *con, buffer *mtime) { + /* + * 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->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) { + size_t used_len; + char *semicolon; + + 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, mtime->ptr, used_len)) { + con->http_status = 304; + return HANDLER_FINISHED; + } else { + char buf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")]; + + /* convert to timestamp */ + if (used_len < sizeof(buf)) { + time_t t_header, t_file; + struct tm tm; + + strncpy(buf, con->request.http_if_modified_since, used_len); + buf[used_len] = '\0'; + + strptime(buf, "%a, %d %b %Y %H:%M:%S GMT", &tm); + t_header = mktime(&tm); + + strptime(mtime->ptr, "%a, %d %b %Y %H:%M:%S GMT", &tm); + t_file = mktime(&tm); + + if (t_file > t_header) { + con->http_status = 304; + return HANDLER_FINISHED; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "ssdd", + "DEBUG: Last-Modified check failed as the received timestamp was too long:", + con->request.http_if_modified_since, used_len, sizeof(buf) - 1); + + con->http_status = 412; + return HANDLER_FINISHED; + } + } + } else { + con->http_status = 304; + return HANDLER_FINISHED; + } + } else { + con->http_status = 412; + return HANDLER_FINISHED; + } + } + } else if (con->request.http_if_modified_since) { + size_t used_len; + char *semicolon; + + 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, mtime->ptr, used_len)) { + con->http_status = 304; + return HANDLER_FINISHED; + } + } + + return HANDLER_GO_ON; +} diff --git a/src/http_auth.c b/src/http_auth.c new file mode 100644 index 0000000..d4de212 --- /dev/null +++ b/src/http_auth.c @@ -0,0 +1,1007 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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 <stdio.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.h" +#endif + + +#ifdef USE_PAM +#include <security/pam_appl.h> +#include <security/pam_misc.h> + +static struct pam_conv conv = { + misc_conv, + NULL +}; +#endif + +handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s); + +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"); + + /* if we get here, the user we got a authed user */ + if (0 == strcmp(require->value->ptr, "valid-user")) { + return 0; + } + + /* 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 */ + + if (0 == strncmp(r, "valid-user", r_len)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules", + require->value); + return -1; + } + + /* search for = in the rules */ + if (NULL == (eq = strchr(r, '='))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "parsing the 'require' section in 'auth.require' failed: a = is missing", + require->value); + return -1; + } + + /* = out of range */ + if (eq > r + r_len) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "parsing the 'require' section in 'auth.require' failed: = out of range", + require->value); + + 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[32]; + char *crypted; + size_t salt_len = 0; + /* + * htpasswd format + * + * user:crypted password + */ + + /* + * Algorithm Salt + * CRYPT_STD_DES 2-character (Default) + * CRYPT_EXT_DES 9-character + * CRYPT_MD5 12-character beginning with $1$ + * CRYPT_BLOWFISH 16-character beginning with $2$ + */ + + if (password->used < 13 + 1) { + fprintf(stderr, "%s.%d\n", __FILE__, __LINE__); + return -1; + } + + if (password->used == 13 + 1) { + /* a simple DES password is 2 + 11 characters */ + salt_len = 2; + } else if (password->ptr[0] == '$' && password->ptr[2] == '$') { + char *dollar = NULL; + + if (NULL == (dollar = strchr(password->ptr + 3, '$'))) { + fprintf(stderr, "%s.%d\n", __FILE__, __LINE__); + return -1; + } + + salt_len = dollar - password->ptr; + } + + if (salt_len > sizeof(salt) - 1) { + fprintf(stderr, "%s.%d\n", __FILE__, __LINE__); + return -1; + } + + strncpy(salt, password->ptr, salt_len); + + salt[salt_len] = '\0'; + + crypted = crypt(pw, salt); + + if (0 == strcmp(password->ptr, crypted)) { + return 0; + } else { + fprintf(stderr, "%s.%d\n", __FILE__, __LINE__); + } + +#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("lighttpd", 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 (p->conf.ldap == NULL || + 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))) { + if (auth_ldap_init(srv, &p->conf) != HANDLER_GO_ON) + return -1; + 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 (p->conf.auth_ldap_starttls == 1) { + if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(ldap, NULL, NULL))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", 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 for", 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; + + 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++) { + /* skip whitespaces */ + while (*c == ' ' || *c == '\t') c++; + if (!c) break; + + 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; + } + + m = get_http_method_name(con->request.http_method); + + /* 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 (qop && 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 && *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__, "sss", + "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 0000000..0b664fa --- /dev/null +++ b/src/http_auth.h @@ -0,0 +1,68 @@ +#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_binddn; + buffer *auth_ldap_bindpw; + buffer *auth_ldap_filter; + buffer *auth_ldap_cafile; + unsigned short auth_ldap_starttls; + + 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 0000000..e440430 --- /dev/null +++ b/src/http_auth_digest.c @@ -0,0 +1,19 @@ +#include <string.h> +#include "http_auth_digest.h" + +#include "buffer.h" + +#ifndef USE_OPENSSL +# 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'; +} + diff --git a/src/http_auth_digest.h b/src/http_auth_digest.h new file mode 100644 index 0000000..8bffce4 --- /dev/null +++ b/src/http_auth_digest.h @@ -0,0 +1,24 @@ +#ifndef _DIGCALC_H_ +#define _DIGCALC_H_ + +#ifdef HAVE_CONFIG_H +#include "config.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 + +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 0000000..c128bf1 --- /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 0000000..4ba24a2 --- /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 0000000..c0b3aa1 --- /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 0000000..fd3c281 --- /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 0000000..dcab955 --- /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 0000000..3701e09 --- /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 0000000..b26588f --- /dev/null +++ b/src/keyvalue.c @@ -0,0 +1,384 @@ +#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_PROPFIND, "PROPFIND" }, + { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, + { HTTP_METHOD_REPORT, "REPORT" }, + { HTTP_METHOD_OPTIONS, "OPTIONS" }, + { HTTP_METHOD_MKCOL, "MKCOL" }, + { HTTP_METHOD_PUT, "PUT" }, + { HTTP_METHOD_DELETE, "DELETE" }, + { HTTP_METHOD_COPY, "COPY" }, + { HTTP_METHOD_MOVE, "MOVE" }, + { HTTP_METHOD_LABEL, "LABEL" }, + { HTTP_METHOD_CHECKOUT, "CHECKOUT" }, + { HTTP_METHOD_CHECKIN, "CHECKIN" }, + { HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" }, + { HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL" }, + { HTTP_METHOD_CONNECT, "CONNECT" }, + + { HTTP_METHOD_UNSET, NULL } +}; + +static keyvalue http_status[] = { + { 100, "Continue" }, + { 101, "Switching Protocols" }, + { 102, "Processing" }, /* WebDAV */ + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non-Authoritative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 207, "Multi-status" }, /* WebDAV */ + { 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" }, + { 422, "Unprocessable Entity" }, /* WebDAV */ + { 423, "Locked" }, /* WebDAV */ + { 424, "Failed Dependency" }, /* WebDAV */ + { 426, "Upgrade Required" }, /* TLS */ + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Not Available" }, + { 504, "Gateway Timeout" }, + { 505, "HTTP Version Not Supported" }, + { 507, "Insufficient Storage" }, /* WebDAV */ + + { -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; + pcre_keyvalue *kv; +#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)); + } + } + + kv = kvb->kv[kvb->used]; + if (NULL == (kv->key = pcre_compile(key, + 0, &errptr, &erroff, NULL))) { + + fprintf(stderr, "%s.%d: rexexp compilation error at %s\n", __FILE__, __LINE__, errptr); + return -1; + } + + if (NULL == (kv->key_extra = pcre_study(kv->key, 0, &errptr)) && + errptr != NULL) { + return -1; + } + + kv->value = buffer_init_string(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; + pcre_keyvalue *kv; + + for (i = 0; i < kvb->size; i++) { + kv = kvb->kv[i]; + if (kv->key) pcre_free(kv->key); + if (kv->key_extra) pcre_free(kv->key_extra); + if (kv->value) buffer_free(kv->value); + free(kv); + } + + if (kvb->kv) free(kvb->kv); +#endif + + free(kvb); +} diff --git a/src/keyvalue.h b/src/keyvalue.h new file mode 100644 index 0000000..e1c940f --- /dev/null +++ b/src/keyvalue.h @@ -0,0 +1,104 @@ +#ifndef _KEY_VALUE_H_ +#define _KEY_VALUE_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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_OPTIONS, + HTTP_METHOD_PROPFIND, /* WebDAV */ + HTTP_METHOD_MKCOL, + HTTP_METHOD_PUT, + HTTP_METHOD_DELETE, + HTTP_METHOD_COPY, + HTTP_METHOD_MOVE, + HTTP_METHOD_PROPPATCH, + HTTP_METHOD_REPORT, /* DeltaV */ + HTTP_METHOD_CHECKOUT, + HTTP_METHOD_CHECKIN, + HTTP_METHOD_VERSION_CONTROL, + HTTP_METHOD_UNCHECKOUT, + HTTP_METHOD_LABEL, + HTTP_METHOD_CONNECT +} 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; + pcre_extra *key_extra; +#endif + + buffer *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 0000000..dd87cdf --- /dev/null +++ b/src/lemon.c @@ -0,0 +1,4396 @@ +/* +** 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 {Bo_FALSE=0, Bo_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 = Bo_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==Bo_FALSE ) break; + } + if( i==rp->nrhs ){ + rp->lhs->lambda = Bo_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==Bo_FALSE ) break; + }else{ + progress += SetUnion(s1->firstset,s2->firstset); + if( s2->lambda==Bo_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 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 */ + struct state *stp; + 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 = Bo_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 = Bo_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 = ¤t; + basis = 0; + basisend = &basis; + Configtable_init(); + return; +} + +/* Initialized the configuration list builder */ +void Configlist_reset(){ + current = 0; + currentend = ¤t; + 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==Bo_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; + size_t 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; + int mnTknOfst, mxTknOfst; + int mnNtOfst, mxNtOfst; + struct axset *ax; + char *name; + + 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 ){ + 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] ){ + 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 global_size = 0; + +/* Set the set size */ +void SetSize(n) +int n; +{ + global_size = n+1; +} + +/* Allocate a new set */ +char *SetNew(){ + char *s; + int i; + s = (char*)malloc( global_size ); + if( s==0 ){ + extern void memory_error(); + memory_error(); + } + for(i=0; i<global_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<global_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 = Bo_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 0000000..ee1edbf --- /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 0000000..402e302 --- /dev/null +++ b/src/log.c @@ -0,0 +1,256 @@ +#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> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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 + * + * we have 3 possibilities: + * - stderr (default) + * - syslog + * - logfile + * + * if the open failed, report to the user and die + * + */ + +int log_error_open(server *srv) { + int fd; + int close_stderr = 1; + +#ifdef HAVE_SYSLOG_H + /* perhaps someone wants to use syslog() */ + openlog("lighttpd", LOG_CONS | LOG_PID, LOG_DAEMON); +#endif + srv->errorlog_mode = ERRORLOG_STDERR; + + if (srv->srvconf.errorlog_use_syslog) { + srv->errorlog_mode = ERRORLOG_SYSLOG; + } else if (!buffer_is_empty(srv->srvconf.errorlog_file)) { + const char *logfile = srv->srvconf.errorlog_file->ptr; + + if (-1 == (srv->errorlog_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->errorlog_fd, F_SETFD, FD_CLOEXEC); +#endif + srv->errorlog_mode = ERRORLOG_FILE; + } + + 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 + if (srv->errorlog_mode == ERRORLOG_STDERR) close_stderr = 0; + + /* 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 (srv->errorlog_mode == ERRORLOG_FILE) { + const char *logfile = srv->srvconf.errorlog_file->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->errorlog_fd); + srv->errorlog_fd = -1; +#ifdef HAVE_SYSLOG_H + srv->errorlog_mode = ERRORLOG_SYSLOG; +#endif + } else { + /* ok, new log is open, close the old one */ + close(srv->errorlog_fd); + srv->errorlog_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"); + + switch(srv->errorlog_mode) { + case ERRORLOG_FILE: + close(srv->errorlog_fd); + break; + case ERRORLOG_SYSLOG: +#ifdef HAVE_SYSLOG_H + closelog(); +#endif + break; + case ERRORLOG_STDERR: + break; + } + + return 0; +} + +int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...) { + va_list ap; + + switch(srv->errorlog_mode) { + case ERRORLOG_FILE: + case ERRORLOG_STDERR: + /* 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->errorlog_buf, srv->ts_debug_str); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, ": ("); + break; + case ERRORLOG_SYSLOG: + /* syslog is generating its own timestamps */ + BUFFER_COPY_STRING_CONST(srv->errorlog_buf, "("); + break; + } + + buffer_append_string(srv->errorlog_buf, filename); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "."); + buffer_append_long(srv->errorlog_buf, line); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, ") "); + + + 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->errorlog_buf, s); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " "); + break; + case 'b': /* buffer */ + b = va_arg(ap, buffer *); + buffer_append_string_buffer(srv->errorlog_buf, b); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " "); + break; + case 'd': /* int */ + d = va_arg(ap, int); + buffer_append_long(srv->errorlog_buf, d); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " "); + break; + case 'o': /* off_t */ + o = va_arg(ap, off_t); + buffer_append_off_t(srv->errorlog_buf, o); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " "); + break; + case 'x': /* int (hex) */ + d = va_arg(ap, int); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "0x"); + buffer_append_long_hex(srv->errorlog_buf, d); + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, " "); + break; + case 'S': /* string */ + s = va_arg(ap, char *); + buffer_append_string(srv->errorlog_buf, s); + break; + case 'B': /* buffer */ + b = va_arg(ap, buffer *); + buffer_append_string_buffer(srv->errorlog_buf, b); + break; + case 'D': /* int */ + d = va_arg(ap, int); + buffer_append_long(srv->errorlog_buf, d); + break; + case '(': + case ')': + case '<': + case '>': + case ',': + case ' ': + buffer_append_string_len(srv->errorlog_buf, fmt, 1); + break; + } + } + va_end(ap); + + switch(srv->errorlog_mode) { + case ERRORLOG_FILE: + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "\n"); + write(srv->errorlog_fd, srv->errorlog_buf->ptr, srv->errorlog_buf->used - 1); + break; + case ERRORLOG_STDERR: + BUFFER_APPEND_STRING_CONST(srv->errorlog_buf, "\n"); + write(STDERR_FILENO, srv->errorlog_buf->ptr, srv->errorlog_buf->used - 1); + break; + case ERRORLOG_SYSLOG: + syslog(LOG_ERR, "%s", srv->errorlog_buf->ptr); + break; + } + + return 0; +} + diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..bffee3a --- /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 0000000..8b688f6 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,355 @@ +/* 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef USE_OPENSSL +#include <string.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 (UINT4 [4], unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, unsigned char *, unsigned int); + +#ifdef HAVE_MEMCPY +#define MD5_memcpy(output, input, len) memcpy((output), (input), (len)) +#else +static void MD5_memcpy (POINTER, POINTER, unsigned int); +#endif +#ifdef HAVE_MEMSET +#define MD5_memset(output, value, len) memset((output), (value), (len)) +#else +static void MD5_memset (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 0000000..a1c0d76 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,47 @@ +/* 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. + */ +#include <limits.h> +#ifdef HAVE_STDINT_H +# include <stdint.h> +#endif +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif + +#define UINT4 uint32_t +#define UINT2 uint16_t +#define POINTER unsigned char * + +/* 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 (MD5_CTX *); +void MD5_Update (MD5_CTX *, unsigned char *, unsigned int); +void MD5_Final (unsigned char [16], MD5_CTX *); + diff --git a/src/mod_access.c b/src/mod_access.c new file mode 100644 index 0000000..f3f7071 --- /dev/null +++ b/src/mod_access.c @@ -0,0 +1,167 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.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 = calloc(1, 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_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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(access_deny); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +URIHANDLER_FUNC(mod_access_uri_handler) { + plugin_data *p = p_d; + int s_len; + size_t k; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_access_patch_connection(srv, con, p); + + 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 we have a case-insensitive FS we have to lower-case the URI here too */ + + if (con->conf.force_lowercase_filenames) { + if (0 == strncasecmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) { + con->http_status = 403; + + return HANDLER_FINISHED; + } + } else { + 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 0000000..bdd0dbc --- /dev/null +++ b/src/mod_accesslog.c @@ -0,0 +1,850 @@ +#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_HTTP_HOST, + 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_HTTP_HOST }, + { '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) continue; + + if (s->access_logbuffer->used) { + if (s->use_syslog) { +# ifdef HAVE_SYSLOG_H + if (s->access_logbuffer->used > 2) { + syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 2, 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 = calloc(1, 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 %V %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__, "sb", + "parsing accesslog-definition failed:", s->format); + + 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) { + /* 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 HANDLER_ERROR; + } + + /* 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 + if (s->access_logbuffer->used > 2) { + /* syslog appends a \n on its own */ + syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 2, 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); + } + + buffer_reset(s->access_logbuffer); + } + + if (s->use_syslog == 0 && + !buffer_is_empty(s->access_logfile) && + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + 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); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +REQUESTDONE_FUNC(log_access_write) { + plugin_data *p = p_d; + buffer *b; + size_t j; + + int newts = 0; + data_string *ds; + + mod_accesslog_patch_connection(srv, con, p); + + b = p->conf.access_logbuffer; + if (b->used == 0) { + buffer_copy_string(b, ""); + } + + 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(b, 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(b, p->conf.ts_accesslog_str); + + break; + case FORMAT_REMOTE_HOST: + + /* handle inet_ntop cache */ + + buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(con->dst_addr))); + + break; + case FORMAT_REMOTE_IDENT: + /* ident */ + BUFFER_APPEND_STRING_CONST(b, "-"); + break; + case FORMAT_REMOTE_USER: + if (con->authed_user->used > 1) { + buffer_append_string_buffer(b, con->authed_user); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_REQUEST_LINE: + if (con->request.request_line->used) { + buffer_append_string_buffer(b, con->request.request_line); + } + break; + case FORMAT_STATUS: + buffer_append_long(b, con->http_status); + break; + + case FORMAT_BYTES_OUT_NO_HEADER: + if (con->bytes_written > 0) { + buffer_append_off_t(b, + con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + 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(b, ds->value); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + 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(b, ds->value); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_FILENAME: + if (con->physical.path->used > 1) { + buffer_append_string_buffer(b, con->physical.path); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_BYTES_OUT: + if (con->bytes_written > 0) { + buffer_append_off_t(b, con->bytes_written); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_BYTES_IN: + if (con->bytes_read > 0) { + buffer_append_off_t(b, con->bytes_read); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_TIME_USED: + buffer_append_long(b, srv->cur_ts - con->request_start); + break; + case FORMAT_SERVER_NAME: + if (con->server_name->used > 1) { + buffer_append_string_buffer(b, con->server_name); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_HTTP_HOST: + if (con->uri.authority->used > 1) { + buffer_append_string_buffer(b, con->uri.authority); + } else { + BUFFER_APPEND_STRING_CONST(b, "-"); + } + break; + case FORMAT_REQUEST_PROTOCOL: + buffer_append_string(b, + con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0"); + break; + case FORMAT_REQUEST_METHOD: + buffer_append_string(b, get_http_method_name(con->request.http_method)); + break; + case FORMAT_SERVER_PORT: + buffer_append_long(b, srv->srvconf.port); + break; + case FORMAT_QUERY_STRING: + buffer_append_string_buffer(b, con->uri.query); + break; + case FORMAT_URL: + buffer_append_string_buffer(b, con->uri.path_raw); + break; + case FORMAT_CONNECTION_STATUS: + switch(con->keep_alive) { + case 0: buffer_append_string(b, "-"); break; + default: buffer_append_string(b, "+"); break; + } + break; + default: + /* + { 'a', FORMAT_REMOTE_ADDR }, + { 'A', FORMAT_LOCAL_ADDR }, + { 'C', FORMAT_COOKIE }, + { 'D', FORMAT_TIME_USED_MS }, + { 'e', FORMAT_ENV }, + */ + + break; + } + break; + default: + break; + } + } + + BUFFER_APPEND_STRING_CONST(b, "\n"); + + if (p->conf.use_syslog || /* syslog doesn't cache */ + (p->conf.access_logfile->used && p->conf.access_logfile->ptr[0] != '|') || /* pipes don't cache */ + newts || + b->used > BUFFER_MAX_REUSE_SIZE) { + if (p->conf.use_syslog) { +#ifdef HAVE_SYSLOG_H + if (b->used > 2) { + /* syslog appends a \n on its own */ + syslog(LOG_INFO, "%*s", b->used - 2, b->ptr); + } +#endif + } else if (p->conf.log_access_fd != -1) { + write(p->conf.log_access_fd, b->ptr, b->used - 1); + } + buffer_reset(b); + } + + 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 0000000..23570e4 --- /dev/null +++ b/src/mod_alias.c @@ -0,0 +1,199 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.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 = calloc(1, 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->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; + } + if (s->alias->used >= 2) { + const array *a = s->alias; + size_t j, k; + + for (j = 0; j < a->used; j ++) { + const buffer *prefix = a->data[a->sorted[j]]->key; + for (k = j + 1; k < a->used; k ++) { + const buffer *key = a->data[a->sorted[k]]->key; + + if (key->used < prefix->used) { + break; + } + if (memcmp(key->ptr, prefix->ptr, prefix->used - 1) != 0) { + break; + } + /* ok, they have same prefix. check position */ + if (a->sorted[j] < a->sorted[k]) { + fprintf(stderr, "url.alias: `%s' will never match as `%s' matched first\n", + key->ptr, + prefix->ptr); + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(alias); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +PHYSICALPATH_FUNC(mod_alias_physical_handler) { + plugin_data *p = p_d; + int uri_len, basedir_len; + char *uri_ptr; + size_t k; + + if (con->physical.path->used == 0) return HANDLER_GO_ON; + + mod_alias_patch_connection(srv, con, p); + + /* not to include the tailing slash */ + basedir_len = (con->physical.basedir->used - 1) - 1; + uri_len = con->physical.path->used - 1 - basedir_len; + uri_ptr = con->physical.path->ptr + basedir_len; + + 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(uri_ptr, ds->key->ptr, alias_len)) { + /* matched */ + + buffer_copy_string_buffer(con->physical.basedir, ds->value); + buffer_copy_string_buffer(srv->tmp_buf, ds->value); + buffer_append_string(srv->tmp_buf, uri_ptr + alias_len); + buffer_copy_string_buffer(con->physical.path, srv->tmp_buf); + + 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_physical= mod_alias_physical_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 0000000..9b791d4 --- /dev/null +++ b/src/mod_auth.c @@ -0,0 +1,630 @@ +#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" + +handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s); + + +/** + * 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]; + + if (!s) continue; + + 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_binddn); + buffer_free(s->auth_ldap_bindpw); + buffer_free(s->auth_ldap_filter); + buffer_free(s->auth_ldap_cafile); + +#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) { + size_t i, j; + mod_auth_plugin_config *s = p->config_storage[0]; + + 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_binddn); + PATCH(auth_ldap_bindpw); + PATCH(auth_ldap_filter); + PATCH(auth_ldap_cafile); + PATCH(auth_ldap_starttls); +#ifdef USE_LDAP + PATCH(ldap); + PATCH(ldap_filter_pre); + PATCH(ldap_filter_post); +#endif + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.ca-file"))) { + PATCH(auth_ldap_cafile); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.starttls"))) { + PATCH(auth_ldap_starttls); + } + } + } + + 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; + + /* select the right config */ + mod_auth_patch_connection(srv, con, p); + + 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++) { + buffer *req = p->conf.auth_require->data[k]->key; + + if (req->used == 0) continue; + if (con->uri.path->used < req->used) continue; + + /* if we have a case-insensitive FS we have to lower-case the URI here too */ + + if (con->conf.force_lowercase_filenames) { + if (0 == strncasecmp(con->uri.path->ptr, req->ptr, req->used - 1)) { + auth_required = 1; + break; + } + } else { + if (0 == strncmp(con->uri.path->ptr, req->ptr, req->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.ldap.ca-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.ldap.starttls", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.ldap.bind-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.ldap.bind-pw", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ + { "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 }, /* 13 */ + { 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++) { + mod_auth_plugin_config *s; + size_t n; + data_array *da; + array *ca; + + s = calloc(1, 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_binddn = buffer_init(); + s->auth_ldap_bindpw = buffer_init(); + s->auth_ldap_filter = buffer_init(); + s->auth_ldap_cafile = buffer_init(); + s->auth_ldap_starttls = 0; + 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_ldap_cafile; + cv[8].destination = &(s->auth_ldap_starttls); + cv[9].destination = s->auth_ldap_binddn; + cv[10].destination = s->auth_ldap_bindpw; + cv[11].destination = s->auth_htdigest_userfile; + cv[12].destination = s->auth_htpasswd_userfile; + cv[13].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; + } else { + if (0 != strcmp(method, "basic") && + 0 != strcmp(method, "digest")) { + log_error_write(srv, __FILE__, __LINE__, "s", "auth.require->method has to be either 'basic' or 'digest'"); + 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: { + handler_t ret = auth_ldap_init(srv, s); + if (ret == HANDLER_ERROR) + return (ret); + break; + } + default: + break; + } + } + + return HANDLER_GO_ON; +} + +handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s) { +#ifdef USE_LDAP + int ret; +#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) { + 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; + } + + if (s->auth_ldap_starttls) { + /* if no CA file is given, it is ok, as we will use encryption + * if the server requires a CAfile it will tell us */ + if (!buffer_is_empty(s->auth_ldap_cafile)) { + if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, + s->auth_ldap_cafile->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "Loading CA certificate failed:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } + } + + if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(s->ldap, NULL, NULL))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } + } + + + /* 1. */ + if (s->auth_ldap_binddn->used) { + if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, s->auth_ldap_binddn->ptr, s->auth_ldap_bindpw->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } + } else { + 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 + 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 0000000..e69de29 --- /dev/null +++ b/src/mod_auth.h diff --git a/src/mod_cgi.c b/src/mod_cgi.c new file mode 100644 index 0000000..883bf98 --- /dev/null +++ b/src/mod_cgi.c @@ -0,0 +1,1305 @@ +#include <sys/types.h> +#ifdef __WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/mman.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 <fcntl.h> + +#include "server.h" +#include "keyvalue.h" +#include "log.h" +#include "connections.h" +#include "joblist.h" +#include "http_chunk.h" + +#include "plugin.h" + +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +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 = calloc(1, 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)); + 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 + + /* the connection to the browser went away, but we still have a connection + * to the CGI script + * + * close cgi-connection + */ + + if (hctx->fd != -1) { + /* 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_GO_ON; + + log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno)); + return HANDLER_ERROR; + default: + /* Send an error if we haven't sent any data yet */ + if (0 == con->file_started) { + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + con->http_status = 500; + con->mode = DIRECT; + } + + if (WIFEXITED(status)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", pid); +#endif + pid = 0; + + return HANDLER_GO_ON; + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid); + pid = 0; + return HANDLER_GO_ON; + } + } + + + kill(pid, SIGTERM); + + /* cgi-script is still alive, queue the PID for removal */ + cgi_pid_add(srv, p, pid); + } +#endif + return HANDLER_GO_ON; +} + +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 + cgi_connection_close(srv, hctx); + + /* if we get a IN|HUP and have read everything don't exec the close twice */ + return HANDLER_FINISHED; + 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, 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 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 + + if (cgi_handler->used > 1) { + /* 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; + const char *s; + 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]); + + /* HACK: + * this is not nice, but it works + * + * we feed the stderr of the CGI to our errorlog, if possible + */ + if (srv->errorlog_mode == ERRORLOG_FILE) { + close(STDERR_FILENO); + dup2(srv->errorlog_fd, STDERR_FILENO); + } + + /* create environment */ + env.ptr = NULL; + env.size = 0; + env.used = 0; + + cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION)); + + if (!buffer_is_empty(con->server_name)) { + cgi_env_add(&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 + cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s)); + } + cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); + + s = get_http_version_name(con->request.http_version); + + cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)); + + 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, strlen(buf)); + +#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 + cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)); + + s = get_http_method_name(con->request.http_method); + cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)); + + if (!buffer_is_empty(con->request.pathinfo)) { + cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); + } + cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); + cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)); + cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); + + +#ifdef HAVE_IPV6 + s = 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 + s = inet_ntoa(con->dst_addr.ipv4.sin_addr); +#endif + cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s)); + + ltostr(buf, +#ifdef HAVE_IPV6 + ntohs(con->dst_addr.plain.sa_family == AF_INET6 ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port) +#else + ntohs(con->dst_addr.ipv4.sin_port) +#endif + ); + cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf)); + + if (!buffer_is_empty(con->authed_user)) { + cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"), + CONST_BUF_LEN(con->authed_user)); + } + + /* request.content_length < SSIZE_MAX, see request.c */ + ltostr(buf, con->request.content_length); + cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)); + cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); + cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); + cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); + + /* for valgrind */ + if (NULL != (s = getenv("LD_PRELOAD"))) { + cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s)); + } + + if (NULL != (s = getenv("LD_LIBRARY_PATH"))) { + cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s)); + } +#ifdef __CYGWIN__ + /* CYGWIN needs SYSTEMROOT */ + if (NULL != (s = getenv("SYSTEMROOT"))) { + cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s)); + } +#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++) { + char cr = '_'; + if (light_isalpha(ds->key->ptr[j])) { + /* upper-case */ + cr = ds->key->ptr[j] & ~32; + } else if (light_isdigit(ds->key->ptr[j])) { + /* copy */ + cr = ds->key->ptr[j]; + } + p->tmp_buf->ptr[p->tmp_buf->used++] = cr; + } + p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; + + cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value)); + } + } + + 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), CONST_BUF_LEN(ds->value)); + } + } + + 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->errorlog_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_length) { + chunkqueue *cq = con->request_content_queue; + chunk *c; + + assert(chunkqueue_length(cq) == (off_t)con->request.content_length); + + /* there is content to send */ + for (c = cq->first; c; c = cq->first) { + int r = 0; + + /* copy all chunks */ + switch(c->type) { + case FILE_CHUNK: + + if (c->file.mmap.start == MAP_FAILED) { + if (-1 == c->file.fd && /* open the file if not already open */ + -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } + + c->file.mmap.length = c->file.length; + + if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.mmap.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", + strerror(errno), c->file.name, c->file.fd); + + return -1; + } + + close(c->file.fd); + c->file.fd = -1; + + /* chunk_reset() or chunk_free() will cleanup for us */ + } + + if ((r = write(to_cgi_fds[1], c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) { + switch(errno) { + case ENOSPC: + con->http_status = 507; + + break; + default: + con->http_status = 403; + break; + } + } + break; + case MEM_CHUNK: + if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) { + switch(errno) { + case ENOSPC: + con->http_status = 507; + + break; + default: + con->http_status = 403; + break; + } + } + break; + case UNUSED_CHUNK: + break; + } + + if (r > 0) { + c->offset += r; + cq->bytes_out += r; + } else { + break; + } + chunkqueue_remove_finished_chunks(cq); + } + } + + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(cgi); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +URIHANDLER_FUNC(cgi_is_handled) { + size_t k, s_len; + plugin_data *p = p_d; + buffer *fn = con->physical.path; + + if (fn->used == 0) return HANDLER_ERROR; + + mod_cgi_patch_connection(srv, con, p); + + 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; + } + /* one handler is enough for the request */ + break; + } + } + + 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: + /* we only have for events here if we don't have the header yet, + * otherwise the event-handler will send us the incoming data */ + if (con->file_started) return HANDLER_FINISHED; + + 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->connection_reset = 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_cml.c b/src/mod_cml.c new file mode 100644 index 0000000..f5b1768 --- /dev/null +++ b/src/mod_cml.c @@ -0,0 +1,325 @@ +#include <sys/stat.h> +#include <time.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> + +#include "buffer.h" +#include "server.h" +#include "log.h" +#include "plugin.h" +#include "response.h" + +#include "stream.h" + +#include "mod_cml.h" + +/* init the plugin data */ +INIT_FUNC(mod_cml_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->basedir = buffer_init(); + p->baseurl = buffer_init(); + p->trigger_handler = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_cml_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->ext); + + buffer_free(s->mc_namespace); + buffer_free(s->power_magnet); + array_free(s->mc_hosts); + +#if defined(HAVE_MEMCACHE_H) + if (s->mc) mc_free(s->mc); +#endif + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->trigger_handler); + buffer_free(p->basedir); + buffer_free(p->baseurl); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_cml_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "cml.extension", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "cml.memcache-hosts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "cml.memcache-namespace", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "cml.power-magnet", NULL, T_CONFIG_STRING, 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->ext = buffer_init(); + s->mc_hosts = array_init(); + s->mc_namespace = buffer_init(); + s->power_magnet = buffer_init(); +#if defined(HAVE_MEMCACHE_H) + s->mc = NULL; +#endif + + cv[0].destination = s->ext; + cv[1].destination = s->mc_hosts; + cv[2].destination = s->mc_namespace; + cv[3].destination = s->power_magnet; + + 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->mc_hosts->used) { +#if defined(HAVE_MEMCACHE_H) + size_t k; + s->mc = mc_new(); + + for (k = 0; k < s->mc_hosts->used; k++) { + data_string *ds = (data_string *)s->mc_hosts->data[k]; + + if (0 != mc_server_add4(s->mc, ds->value->ptr)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "connection to host failed:", + ds->value); + + return HANDLER_ERROR; + } + } +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "memcache support is not compiled in but cml.memcache-hosts is set, aborting"); + return HANDLER_ERROR; +#endif + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_cml_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(ext); +#if defined(HAVE_MEMCACHE_H) + PATCH(mc); +#endif + PATCH(mc_namespace); + PATCH(power_magnet); + + /* 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]; + s = p->config_storage[i]; + + /* 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("cml.extension"))) { + PATCH(ext); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.memcache-hosts"))) { +#if defined(HAVE_MEMCACHE_H) + PATCH(mc); +#endif + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.memcache-namespace"))) { + PATCH(mc_namespace); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cml.power-magnet"))) { + PATCH(power_magnet); + } + } + } + + return 0; +} +#undef PATCH + +int cache_call_lua(server *srv, connection *con, plugin_data *p, buffer *cml_file) { + buffer *b; + char *c; + int ret; + + /* cleanup basedir */ + b = p->baseurl; + buffer_copy_string_buffer(b, con->uri.path); + for (c = b->ptr + b->used - 1; c > b->ptr && *c != '/'; c--); + + if (*c == '/') { + b->used = c - b->ptr + 2; + *(c+1) = '\0'; + } + + b = p->basedir; + buffer_copy_string_buffer(b, con->physical.path); + for (c = b->ptr + b->used - 1; c > b->ptr && *c != '/'; c--); + + if (*c == '/') { + b->used = c - b->ptr + 2; + *(c+1) = '\0'; + } + + + /* prepare variables + * - cookie-based + * - get-param-based + */ + + return cache_parse_lua(srv, con, p, cml_file); + +} + +URIHANDLER_FUNC(mod_cml_power_magnet) { + plugin_data *p = p_d; + + mod_cml_patch_connection(srv, con, p); + + buffer_reset(p->basedir); + buffer_reset(p->baseurl); + buffer_reset(p->trigger_handler); + + if (buffer_is_empty(p->conf.power_magnet)) return HANDLER_GO_ON; + + /* + * power-magnet: + * cml.power-magnet = server.docroot + "/rewrite.cml" + * + * is called on EACH request, take the original REQUEST_URI and modifies the + * request header as neccesary. + * + * First use: + * if file_exists("/maintainance.html") { + * output_include = ( "/maintainance.html" ) + * return CACHE_HIT + * } + * + * as we only want to rewrite HTML like requests we should cover it in a conditional + * + * */ + + switch(cache_call_lua(srv, con, p, p->conf.power_magnet)) { + case -1: + /* error */ + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "cache-error"); + } + con->http_status = 500; + return HANDLER_COMEBACK; + case 0: + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "cache-hit"); + } + /* cache-hit */ + buffer_reset(con->physical.path); + return HANDLER_FINISHED; + case 1: + /* cache miss */ + return HANDLER_GO_ON; + default: + con->http_status = 500; + return HANDLER_COMEBACK; + } +} + +URIHANDLER_FUNC(mod_cml_is_handled) { + plugin_data *p = p_d; + + if (buffer_is_empty(con->physical.path)) return HANDLER_ERROR; + + mod_cml_patch_connection(srv, con, p); + + buffer_reset(p->basedir); + buffer_reset(p->baseurl); + buffer_reset(p->trigger_handler); + + if (buffer_is_empty(p->conf.ext)) return HANDLER_GO_ON; + + if (!buffer_is_equal_right_len(con->physical.path, p->conf.ext, p->conf.ext->used - 1)) { + return HANDLER_GO_ON; + } + + switch(cache_call_lua(srv, con, p, con->physical.path)) { + case -1: + /* error */ + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "cache-error"); + } + con->http_status = 500; + return HANDLER_COMEBACK; + case 0: + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "cache-hit"); + } + /* cache-hit */ + buffer_reset(con->physical.path); + return HANDLER_FINISHED; + case 1: + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "cache-miss"); + } + /* cache miss */ + return HANDLER_COMEBACK; + default: + con->http_status = 500; + return HANDLER_COMEBACK; + } +} + +int mod_cml_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("cache"); + + p->init = mod_cml_init; + p->cleanup = mod_cml_free; + p->set_defaults = mod_cml_set_defaults; + + p->handle_subrequest_start = mod_cml_is_handled; + p->handle_physical = mod_cml_power_magnet; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_cml.h b/src/mod_cml.h new file mode 100644 index 0000000..992af68 --- /dev/null +++ b/src/mod_cml.h @@ -0,0 +1,43 @@ +#ifndef _MOD_CACHE_H_ +#define _MOD_CACHE_H_ + +#include "buffer.h" +#include "server.h" +#include "response.h" + +#include "stream.h" +#include "plugin.h" + +#if defined(HAVE_MEMCACHE_H) +#include <memcache.h> +#endif + +#define plugin_data mod_cache_plugin_data + +typedef struct { + buffer *ext; + + array *mc_hosts; + buffer *mc_namespace; +#if defined(HAVE_MEMCACHE_H) + struct memcache *mc; +#endif + buffer *power_magnet; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *basedir; + buffer *baseurl; + + buffer *trigger_handler; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn); + +#endif diff --git a/src/mod_cml_funcs.c b/src/mod_cml_funcs.c new file mode 100644 index 0000000..46a1f16 --- /dev/null +++ b/src/mod_cml_funcs.c @@ -0,0 +1,301 @@ +#include <sys/stat.h> +#include <time.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <dirent.h> +#include <stdio.h> + +#include "buffer.h" +#include "server.h" +#include "log.h" +#include "plugin.h" +#include "response.h" + +#include "mod_cml.h" +#include "mod_cml_funcs.h" + +#ifdef USE_OPENSSL +# include <openssl/md5.h> +#else +# 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 + +#ifdef HAVE_LUA_H + +int f_crypto_md5(lua_State *L) { + MD5_CTX Md5Ctx; + HASH HA1; + buffer b; + char hex[33]; + int n = lua_gettop(L); + + b.ptr = hex; + b.used = 0; + b.size = sizeof(hex); + + if (n != 1) { + lua_pushstring(L, "md5: expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "md5: argument has to be a string"); + lua_error(L); + } + + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)lua_tostring(L, 1), lua_strlen(L, 1)); + MD5_Final(HA1, &Md5Ctx); + + buffer_copy_string_hex(&b, (char *)HA1, 16); + + lua_pushstring(L, b.ptr); + + return 1; +} + + +int f_file_mtime(lua_State *L) { + struct stat st; + int n = lua_gettop(L); + + if (n != 1) { + lua_pushstring(L, "file_mtime: expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "file_mtime: argument has to be a string"); + lua_error(L); + } + + if (-1 == stat(lua_tostring(L, 1), &st)) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, st.st_mtime); + + return 1; +} + +int f_dir_files_iter(lua_State *L) { + DIR *d; + struct dirent *de; + + d = lua_touserdata(L, lua_upvalueindex(1)); + + if (NULL == (de = readdir(d))) { + /* EOF */ + closedir(d); + + return 0; + } else { + lua_pushstring(L, de->d_name); + return 1; + } +} + +int f_dir_files(lua_State *L) { + DIR *d; + int n = lua_gettop(L); + + if (n != 1) { + lua_pushstring(L, "dir_files: expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "dir_files: argument has to be a string"); + lua_error(L); + } + + /* check if there is a valid DIR handle on the stack */ + if (NULL == (d = opendir(lua_tostring(L, 1)))) { + lua_pushnil(L); + return 1; + } + + /* push d into registry */ + lua_pushlightuserdata(L, d); + lua_pushcclosure(L, f_dir_files_iter, 1); + + return 1; +} + +int f_file_isreg(lua_State *L) { + struct stat st; + int n = lua_gettop(L); + + if (n != 1) { + lua_pushstring(L, "file_isreg: expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "file_isreg: argument has to be a string"); + lua_error(L); + } + + if (-1 == stat(lua_tostring(L, 1), &st)) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, S_ISREG(st.st_mode)); + + return 1; +} + +int f_file_isdir(lua_State *L) { + struct stat st; + int n = lua_gettop(L); + + if (n != 1) { + lua_pushstring(L, "file_isreg: expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "file_isreg: argument has to be a string"); + lua_error(L); + } + + if (-1 == stat(lua_tostring(L, 1), &st)) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, S_ISDIR(st.st_mode)); + + return 1; +} + + + +#ifdef HAVE_MEMCACHE_H +int f_memcache_exists(lua_State *L) { + char *r; + int n = lua_gettop(L); + struct memcache *mc; + + if (!lua_islightuserdata(L, lua_upvalueindex(1))) { + lua_pushstring(L, "where is my userdata ?"); + lua_error(L); + } + + mc = lua_touserdata(L, lua_upvalueindex(1)); + + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); + } + + if (NULL == (r = mc_aget(mc, + lua_tostring(L, 1), lua_strlen(L, 1)))) { + + lua_pushboolean(L, 0); + return 1; + } + + free(r); + + lua_pushboolean(L, 1); + return 1; +} + +int f_memcache_get_string(lua_State *L) { + char *r; + int n = lua_gettop(L); + + struct memcache *mc; + + if (!lua_islightuserdata(L, lua_upvalueindex(1))) { + lua_pushstring(L, "where is my userdata ?"); + lua_error(L); + } + + mc = lua_touserdata(L, lua_upvalueindex(1)); + + + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); + } + + if (NULL == (r = mc_aget(mc, + lua_tostring(L, 1), lua_strlen(L, 1)))) { + lua_pushnil(L); + return 1; + } + + lua_pushstring(L, r); + + free(r); + + return 1; +} + +int f_memcache_get_long(lua_State *L) { + char *r; + int n = lua_gettop(L); + + struct memcache *mc; + + if (!lua_islightuserdata(L, lua_upvalueindex(1))) { + lua_pushstring(L, "where is my userdata ?"); + lua_error(L); + } + + mc = lua_touserdata(L, lua_upvalueindex(1)); + + + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); + } + + if (NULL == (r = mc_aget(mc, + lua_tostring(L, 1), lua_strlen(L, 1)))) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, strtol(r, NULL, 10)); + + free(r); + + return 1; +} +#endif + +#endif diff --git a/src/mod_cml_funcs.h b/src/mod_cml_funcs.h new file mode 100644 index 0000000..42e067c --- /dev/null +++ b/src/mod_cml_funcs.h @@ -0,0 +1,21 @@ +#ifndef _MOD_CML_FUNCS_H_ +#define _MOD_CML_FUNCS_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_LUA_H +#include <lua.h> + +int f_crypto_md5(lua_State *L); +int f_file_mtime(lua_State *L); +int f_file_isreg(lua_State *L); +int f_file_isdir(lua_State *L); +int f_dir_files(lua_State *L); + +int f_memcache_exists(lua_State *L); +int f_memcache_get_string(lua_State *L); +int f_memcache_get_long(lua_State *L); +#endif +#endif diff --git a/src/mod_cml_lua.c b/src/mod_cml_lua.c new file mode 100644 index 0000000..76e979f --- /dev/null +++ b/src/mod_cml_lua.c @@ -0,0 +1,468 @@ +#include <assert.h> +#include <stdio.h> +#include <errno.h> +#include <time.h> + +#include "mod_cml.h" +#include "mod_cml_funcs.h" +#include "log.h" +#include "stream.h" + +#include "stat_cache.h" + +#ifdef USE_OPENSSL +# include <openssl/md5.h> +#else +# 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 + +#ifdef HAVE_LUA_H + +#include <lua.h> +#include <lualib.h> + +typedef struct { + stream st; + int done; +} readme; + +static const char * load_file(lua_State *L, void *data, size_t *size) { + readme *rm = data; + + UNUSED(L); + + if (rm->done) return 0; + + *size = rm->st.size; + rm->done = 1; + return rm->st.start; +} + +static int lua_to_c_get_string(lua_State *L, const char *varname, buffer *b) { + int curelem; + + lua_pushstring(L, varname); + + curelem = lua_gettop(L); + lua_gettable(L, LUA_GLOBALSINDEX); + + /* it should be a table */ + if (!lua_isstring(L, curelem)) { + lua_settop(L, curelem - 1); + + return -1; + } + + buffer_copy_string(b, lua_tostring(L, curelem)); + + lua_pop(L, 1); + + assert(curelem - 1 == lua_gettop(L)); + + return 0; +} + +static int lua_to_c_is_table(lua_State *L, const char *varname) { + int curelem; + + lua_pushstring(L, varname); + + curelem = lua_gettop(L); + lua_gettable(L, LUA_GLOBALSINDEX); + + /* it should be a table */ + if (!lua_istable(L, curelem)) { + lua_settop(L, curelem - 1); + + return 0; + } + + lua_settop(L, curelem - 1); + + assert(curelem - 1 == lua_gettop(L)); + + return 1; +} + +static int c_to_lua_push(lua_State *L, int tbl, const char *key, size_t key_len, const char *val, size_t val_len) { + lua_pushlstring(L, key, key_len); + lua_pushlstring(L, val, val_len); + lua_settable(L, tbl); + + return 0; +} + + +int cache_export_get_params(lua_State *L, int tbl, buffer *qrystr) { + size_t is_key = 1; + size_t i; + char *key = NULL, *val = NULL; + + key = qrystr->ptr; + + /* we need the \0 */ + for (i = 0; i < qrystr->used; i++) { + switch(qrystr->ptr[i]) { + case '=': + if (is_key) { + val = qrystr->ptr + i + 1; + + qrystr->ptr[i] = '\0'; + + is_key = 0; + } + + break; + case '&': + case '\0': /* fin symbol */ + if (!is_key) { + /* we need at least a = since the last & */ + + /* terminate the value */ + qrystr->ptr[i] = '\0'; + + c_to_lua_push(L, tbl, + key, strlen(key), + val, strlen(val)); + } + + key = qrystr->ptr + i + 1; + val = NULL; + is_key = 1; + break; + } + } + + return 0; +} +#if 0 +int cache_export_cookie_params(server *srv, connection *con, plugin_data *p) { + data_unset *d; + + UNUSED(srv); + + if (NULL != (d = array_get_element(con->request.headers, "Cookie"))) { + data_string *ds = (data_string *)d; + size_t key = 0, value = 0; + size_t is_key = 1, is_sid = 0; + size_t i; + + /* found COOKIE */ + if (!DATA_IS_STRING(d)) return -1; + if (ds->value->used == 0) return -1; + + if (ds->value->ptr[0] == '\0' || + ds->value->ptr[0] == '=' || + ds->value->ptr[0] == ';') return -1; + + buffer_reset(p->session_id); + for (i = 0; i < ds->value->used; i++) { + switch(ds->value->ptr[i]) { + case '=': + if (is_key) { + if (0 == strncmp(ds->value->ptr + key, "PHPSESSID", i - key)) { + /* found PHP-session-id-key */ + is_sid = 1; + } + value = i + 1; + + is_key = 0; + } + + break; + case ';': + if (is_sid) { + buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value); + } + + is_sid = 0; + key = i + 1; + value = 0; + is_key = 1; + break; + case ' ': + if (is_key == 1 && key == i) key = i + 1; + if (is_key == 0 && value == i) value = i + 1; + break; + case '\0': + if (is_sid) { + buffer_copy_string_len(p->session_id, ds->value->ptr + value, i - value); + } + /* fin */ + break; + } + } + } + + return 0; +} +#endif + +int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { + lua_State *L; + readme rm; + int ret = -1; + buffer *b = buffer_init(); + int header_tbl = 0; + + rm.done = 0; + stream_open(&rm.st, fn); + + /* push the lua file to the interpreter and see what happends */ + L = lua_open(); + + luaopen_base(L); + luaopen_table(L); + luaopen_string(L); + luaopen_math(L); + luaopen_io(L); + + /* register functions */ + lua_register(L, "md5", f_crypto_md5); + lua_register(L, "file_mtime", f_file_mtime); + lua_register(L, "file_isreg", f_file_isreg); + lua_register(L, "file_isdir", f_file_isreg); + lua_register(L, "dir_files", f_dir_files); + +#ifdef HAVE_MEMCACHE_H + lua_pushliteral(L, "memcache_get_long"); + lua_pushlightuserdata(L, p->conf.mc); + lua_pushcclosure(L, f_memcache_get_long, 1); + lua_settable(L, LUA_GLOBALSINDEX); + + lua_pushliteral(L, "memcache_get_string"); + lua_pushlightuserdata(L, p->conf.mc); + lua_pushcclosure(L, f_memcache_get_string, 1); + lua_settable(L, LUA_GLOBALSINDEX); + + lua_pushliteral(L, "memcache_exists"); + lua_pushlightuserdata(L, p->conf.mc); + lua_pushcclosure(L, f_memcache_exists, 1); + lua_settable(L, LUA_GLOBALSINDEX); +#endif + /* register CGI environment */ + lua_pushliteral(L, "request"); + lua_newtable(L); + lua_settable(L, LUA_GLOBALSINDEX); + + lua_pushliteral(L, "request"); + header_tbl = lua_gettop(L); + lua_gettable(L, LUA_GLOBALSINDEX); + + c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); + c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); + c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); + c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); + if (!buffer_is_empty(con->request.pathinfo)) { + c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); + } + + c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir)); + c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl)); + + /* register GET parameter */ + lua_pushliteral(L, "get"); + lua_newtable(L); + lua_settable(L, LUA_GLOBALSINDEX); + + lua_pushliteral(L, "get"); + header_tbl = lua_gettop(L); + lua_gettable(L, LUA_GLOBALSINDEX); + + buffer_copy_string_buffer(b, con->uri.query); + cache_export_get_params(L, header_tbl, b); + buffer_reset(b); + + /* 2 default constants */ + lua_pushliteral(L, "CACHE_HIT"); + lua_pushboolean(L, 0); + lua_settable(L, LUA_GLOBALSINDEX); + + lua_pushliteral(L, "CACHE_MISS"); + lua_pushboolean(L, 1); + lua_settable(L, LUA_GLOBALSINDEX); + + /* load lua program */ + if (lua_load(L, load_file, &rm, fn->ptr) || lua_pcall(L,0,1,0)) { + log_error_write(srv, __FILE__, __LINE__, "s", + lua_tostring(L,-1)); + + goto error; + } + + /* get return value */ + ret = (int)lua_tonumber(L, -1); + lua_pop(L, 1); + + /* fetch the data from lua */ + lua_to_c_get_string(L, "trigger_handler", p->trigger_handler); + + if (0 == lua_to_c_get_string(L, "output_contenttype", b)) { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b)); + } + + if (ret == 0) { + /* up to now it is a cache-hit, check if all files exist */ + + int curelem; + time_t mtime = 0; + + if (!lua_to_c_is_table(L, "output_include")) { + log_error_write(srv, __FILE__, __LINE__, "s", + "output_include is missing or not a table"); + ret = -1; + + goto error; + } + + lua_pushstring(L, "output_include"); + + curelem = lua_gettop(L); + lua_gettable(L, LUA_GLOBALSINDEX); + + /* HOW-TO build a etag ? + * as we don't just have one file we have to take the stat() + * from all base files, merge them and build the etag from + * it later. + * + * The mtime of the content is the mtime of the freshest base file + * + * */ + + lua_pushnil(L); /* first key */ + while (lua_next(L, curelem) != 0) { + stat_cache_entry *sce = NULL; + /* key' is at index -2 and value' at index -1 */ + + if (lua_isstring(L, -1)) { + const char *s = lua_tostring(L, -1); + + /* the file is relative, make it absolute */ + if (s[0] != '/') { + buffer_copy_string_buffer(b, p->basedir); + buffer_append_string(b, lua_tostring(L, -1)); + } else { + buffer_copy_string(b, lua_tostring(L, -1)); + } + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) { + /* stat failed */ + + switch(errno) { + case ENOENT: + /* a file is missing, call the handler to generate it */ + if (!buffer_is_empty(p->trigger_handler)) { + ret = 1; /* cache-miss */ + + log_error_write(srv, __FILE__, __LINE__, "s", + "a file is missing, calling handler"); + + break; + } else { + /* handler not set -> 500 */ + ret = -1; + + log_error_write(srv, __FILE__, __LINE__, "s", + "a file missing and no handler set"); + + break; + } + break; + default: + break; + } + } else { + chunkqueue_append_file(con->write_queue, b, 0, sce->st.st_size); + if (sce->st.st_mtime > mtime) mtime = sce->st.st_mtime; + } + } else { + /* not a string */ + ret = -1; + log_error_write(srv, __FILE__, __LINE__, "s", + "not a string"); + break; + } + + lua_pop(L, 1); /* removes value'; keeps key' for next iteration */ + } + + lua_settop(L, curelem - 1); + + if (ret == 0) { + data_string *ds; + char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")]; + buffer tbuf; + + con->file_finished = 1; + + ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"); + + /* no Last-Modified specified */ + if ((mtime) && (NULL == ds)) { + + strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime)); + + response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1); + + + tbuf.ptr = timebuf; + tbuf.used = sizeof(timebuf); + tbuf.size = sizeof(timebuf); + } else if (ds) { + tbuf.ptr = ds->value->ptr; + tbuf.used = ds->value->used; + tbuf.size = ds->value->size; + } else { + tbuf.size = 0; + tbuf.used = 0; + tbuf.ptr = NULL; + } + + if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, &tbuf)) { + /* ok, the client already has our content, + * no need to send it again */ + + chunkqueue_reset(con->write_queue); + ret = 0; /* cache-hit */ + } + } else { + chunkqueue_reset(con->write_queue); + } + } + + if (ret == 1 && !buffer_is_empty(p->trigger_handler)) { + /* cache-miss */ + buffer_copy_string_buffer(con->uri.path, p->baseurl); + buffer_append_string_buffer(con->uri.path, p->trigger_handler); + + buffer_copy_string_buffer(con->physical.path, p->basedir); + buffer_append_string_buffer(con->physical.path, p->trigger_handler); + + chunkqueue_reset(con->write_queue); + } + +error: + lua_close(L); + + stream_close(&rm.st); + buffer_free(b); + + return ret /* cache-error */; +} +#else +int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { + /* error */ + return -1; +} +#endif diff --git a/src/mod_compress.c b/src/mod_compress.c new file mode 100644 index 0000000..fc78220 --- /dev/null +++ b/src/mod_compress.c @@ -0,0 +1,701 @@ +#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 "stat_cache.h" + +#include "plugin.h" + +#include "crc32.h" +#include "etag.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]; + + if (!s) continue; + + 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; + + 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 = calloc(1, 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->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 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + + 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 HANDLER_GO_ON; + +} + +#ifdef USE_ZLIB +static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, 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 = (unsigned char *)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, stat_cache_entry *sce, int type) { + int ifd, ofd; + int ret = -1; + void *start; + const char *filename = fn->ptr; + ssize_t r; + + /* overflow */ + if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1; + + /* don't mmap files > 128Mb + * + * we could use a sliding window, but currently there is no need for it + */ + + if (sce->st.st_size > 128 * 1024 * 1024) 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__, "sbss", "creating cache-directory", p->b, "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, sce->etag); + + if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 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 | O_BINARY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno)); + + close(ofd); + + return -1; + } + + + if (MAP_FAILED == (start = mmap(NULL, sce->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, sce->st.st_size, sce->st.st_mtime); + break; + case HTTP_ACCEPT_ENCODING_DEFLATE: + ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size); + break; +#endif +#ifdef USE_BZ2LIB + case HTTP_ACCEPT_ENCODING_BZIP2: + ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size); + break; +#endif + default: + ret = -1; + break; + } + + if (-1 == (r = write(ofd, p->b->ptr, p->b->used))) { + munmap(start, sce->st.st_size); + close(ofd); + close(ifd); + return -1; + } + + if ((size_t)r != p->b->used) { + + } + + munmap(start, sce->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, stat_cache_entry *sce, int type) { + int ifd; + int ret = -1; + void *start; + buffer *b; + + /* overflow */ + if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1; + + /* don't mmap files > 128M + * + * we could use a sliding window, but currently there is no need for it + */ + + if (sce->st.st_size > 128 * 1024 * 1024) return -1; + + + if (-1 == (ifd = open(fn->ptr, O_RDONLY | O_BINARY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno)); + + return -1; + } + + + if (MAP_FAILED == (start = mmap(NULL, sce->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, sce->st.st_size, sce->st.st_mtime); + break; + case HTTP_ACCEPT_ENCODING_DEFLATE: + ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size); + break; +#endif +#ifdef USE_BZ2LIB + case HTTP_ACCEPT_ENCODING_BZIP2: + ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size); + break; +#endif + default: + ret = -1; + break; + } + + munmap(start, sce->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 + 1); + + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(compress_cache_dir); + PATCH(compress); + PATCH(compress_max_filesize); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +PHYSICALPATH_FUNC(mod_compress_physical) { + plugin_data *p = p_d; + size_t m; + off_t max_fsize; + stat_cache_entry *sce = NULL; + + /* 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; + } + + if (buffer_is_empty(con->physical.path)) { + return HANDLER_GO_ON; + } + + mod_compress_patch_connection(srv, con, p); + + max_fsize = p->conf.compress_max_filesize; + + stat_cache_get_entry(srv, con, con->physical.path, &sce); + + /* don't compress files that are too large as we need to much time to handle them */ + if (max_fsize && (sce->st.st_size >> 10) > max_fsize) 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, sce->content_type)) { + /* 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, sce, compression_type)) { + buffer *mtime; + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name)); + + mtime = strftime_cache_get(srv, sce->st.st_mtime); + response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); + + etag_mutate(con->physical.etag, sce->etag); + response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); + + return HANDLER_GO_ON; + } + } else if (0 == deflate_file_to_buffer(srv, con, p, + con->physical.path, sce, compression_type)) { + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name)); + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); + + 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_subrequest_start = mod_compress_physical; + p->cleanup = mod_compress_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_dirlisting.c b/src/mod_dirlisting.c new file mode 100644 index 0000000..69eb1e9 --- /dev/null +++ b/src/mod_dirlisting.c @@ -0,0 +1,888 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <time.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "response.h" +#include "stat_cache.h" +#include "stream.h" + +/** + * this is a dirlisting for a lighttpd plugin + */ + + +#ifdef HAVE_SYS_SYSLIMITS_H +#include <sys/syslimits.h> +#endif + +#ifdef HAVE_ATTR_ATTRIBUTES_H +#include <attr/attributes.h> +#endif + +/* plugin config for all request/connections */ + +typedef struct { +#ifdef HAVE_PCRE_H + pcre *regex; +#endif + buffer *string; +} excludes; + +typedef struct { + excludes **ptr; + + size_t used; + size_t size; +} excludes_buffer; + +typedef struct { + unsigned short dir_listing; + unsigned short hide_dot_files; + unsigned short show_readme; + unsigned short hide_readme_file; + unsigned short show_header; + unsigned short hide_header_file; + + excludes_buffer *excludes; + + buffer *external_css; + buffer *encoding; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + buffer *content_charset; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +excludes_buffer *excludes_buffer_init(void) { + excludes_buffer *exb; + + exb = calloc(1, sizeof(*exb)); + + return exb; +} + +int excludes_buffer_append(excludes_buffer *exb, buffer *string) { +#ifdef HAVE_PCRE_H + size_t i; + const char *errptr; + int erroff; + + if (!string) return -1; + + if (exb->size == 0) { + exb->size = 4; + exb->used = 0; + + exb->ptr = malloc(exb->size * sizeof(*exb->ptr)); + + for(i = 0; i < exb->size ; i++) { + exb->ptr[i] = calloc(1, sizeof(**exb->ptr)); + } + } else if (exb->used == exb->size) { + exb->size += 4; + + exb->ptr = realloc(exb->ptr, exb->size * sizeof(*exb->ptr)); + + for(i = exb->used; i < exb->size; i++) { + exb->ptr[i] = calloc(1, sizeof(**exb->ptr)); + } + } + + + if (NULL == (exb->ptr[exb->used]->regex = pcre_compile(string->ptr, 0, + &errptr, &erroff, NULL))) { + return -1; + } + + exb->ptr[exb->used]->string = buffer_init(); + buffer_copy_string_buffer(exb->ptr[exb->used]->string, string); + + exb->used++; + + return 0; +#else + UNUSED(exb); + UNUSED(string); + + return -1; +#endif +} + +void excludes_buffer_free(excludes_buffer *exb) { +#ifdef HAVE_PCRE_H + size_t i; + + for (i = 0; i < exb->size; i++) { + if (exb->ptr[i]->regex) pcre_free(exb->ptr[i]->regex); + if (exb->ptr[i]->string) buffer_free(exb->ptr[i]->string); + free(exb->ptr[i]); + } + + if (exb->ptr) free(exb->ptr); +#endif + + free(exb); +} + +/* init the plugin data */ +INIT_FUNC(mod_dirlisting_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + p->content_charset = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_dirlisting_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) continue; + + excludes_buffer_free(s->excludes); + buffer_free(s->external_css); + buffer_free(s->encoding); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->tmp_buf); + buffer_free(p->content_charset); + + free(p); + + return HANDLER_GO_ON; +} + +static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option) { + data_unset *du; + + if (NULL != (du = array_get_element(ca, option))) { + data_array *da = (data_array *)du; + size_t j; + + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", option, "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: ", option, "[", + da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + if (0 != excludes_buffer_append(s->excludes, + ((data_string *)(da->value->data[j]))->value)) { +#ifdef HAVE_PCRE_H + log_error_write(srv, __FILE__, __LINE__, "sb", + "pcre-compile failed for", ((data_string *)(da->value->data[j]))->value); +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "pcre support is missing, please install libpcre and the headers"); +#endif + } + } + } + + return 0; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_dirlisting_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "dir-listing.exclude", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "dir-listing.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "dir-listing.hide-dotfiles", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "dir-listing.external-css", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { "dir-listing.encoding", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ + { "dir-listing.show-readme", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ + { "dir-listing.hide-readme-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ + { "dir-listing.show-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ + { "dir-listing.hide-header-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */ + { "server.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ + + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + 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 = calloc(1, sizeof(plugin_config)); + s->excludes = excludes_buffer_init(); + s->dir_listing = 0; + s->external_css = buffer_init(); + s->hide_dot_files = 0; + s->show_readme = 0; + s->hide_readme_file = 0; + s->show_header = 0; + s->hide_header_file = 0; + s->encoding = buffer_init(); + + cv[0].destination = s->excludes; + cv[1].destination = &(s->dir_listing); + cv[2].destination = &(s->hide_dot_files); + cv[3].destination = s->external_css; + cv[4].destination = s->encoding; + cv[5].destination = &(s->show_readme); + cv[6].destination = &(s->hide_readme_file); + cv[7].destination = &(s->show_header); + cv[8].destination = &(s->hide_header_file); + cv[9].destination = &(s->dir_listing); /* old name */ + + 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; + } + + parse_config_entry(srv, s, ca, "dir-listing.exclude"); + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_dirlisting_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(dir_listing); + PATCH(external_css); + PATCH(hide_dot_files); + PATCH(encoding); + PATCH(show_readme); + PATCH(hide_readme_file); + PATCH(show_header); + PATCH(hide_header_file); + PATCH(excludes); + + /* 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]; + s = p->config_storage[i]; + + /* 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("dir-listing.activate")) || + 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("dir-listing.hide-dotfiles"))) { + PATCH(hide_dot_files); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.external-css"))) { + PATCH(external_css); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.encoding"))) { + PATCH(encoding); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.show-readme"))) { + PATCH(show_readme); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-readme-file"))) { + PATCH(hide_readme_file); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.show-header"))) { + PATCH(show_header); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.hide-header-file"))) { + PATCH(hide_header_file); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("dir-listing.excludes"))) { + PATCH(excludes); + } + } + } + + return 0; +} +#undef PATCH + +typedef struct { + size_t namelen; + time_t mtime; + off_t size; +} dirls_entry_t; + +typedef struct { + dirls_entry_t **ent; + size_t used; + size_t size; +} dirls_list_t; + +#define DIRLIST_ENT_NAME(ent) ((char*)(ent) + sizeof(dirls_entry_t)) +#define DIRLIST_BLOB_SIZE 16 + +/* simple combsort algorithm */ +static void http_dirls_sort(dirls_entry_t **ent, int num) { + int gap = num; + int i, j; + int swapped; + dirls_entry_t *tmp; + + do { + gap = (gap * 10) / 13; + if (gap == 9 || gap == 10) + gap = 11; + if (gap < 1) + gap = 1; + swapped = 0; + + for (i = 0; i < num - gap; i++) { + j = i + gap; + if (strcmp(DIRLIST_ENT_NAME(ent[i]), DIRLIST_ENT_NAME(ent[j])) > 0) { + tmp = ent[i]; + ent[i] = ent[j]; + ent[j] = tmp; + swapped = 1; + } + } + + } while (gap > 1 || swapped); +} + +/* buffer must be able to hold "999.9K" + * conversion is simple but not perfect + */ +static int http_list_directory_sizefmt(char *buf, off_t size) { + const char unit[] = "KMGTPE"; /* Kilo, Mega, Tera, Peta, Exa */ + const char *u = unit - 1; /* u will always increment at least once */ + int remain; + char *out = buf; + + if (size < 100) + size += 99; + if (size < 100) + size = 0; + + while (1) { + remain = (int) size & 1023; + size >>= 10; + u++; + if ((size & (~0 ^ 1023)) == 0) + break; + } + + remain /= 100; + if (remain > 9) + remain = 9; + if (size > 999) { + size = 0; + remain = 9; + u++; + } + + out += ltostr(out, size); + out[0] = '.'; + out[1] = remain + '0'; + out[2] = *u; + out[3] = '\0'; + + return (out + 3 - buf); +} + +static void http_list_directory_header(server *srv, connection *con, plugin_data *p, buffer *out) { + UNUSED(srv); + + BUFFER_APPEND_STRING_CONST(out, + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n" + "<head>\n" + "<title>Index of " + ); + buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML); + BUFFER_APPEND_STRING_CONST(out, "</title>\n"); + + if (p->conf.external_css->used > 1) { + BUFFER_APPEND_STRING_CONST(out, "<link rel=\"stylesheet\" type=\"text/css\" href=\""); + buffer_append_string_buffer(out, p->conf.external_css); + BUFFER_APPEND_STRING_CONST(out, "\" />\n"); + } else { + BUFFER_APPEND_STRING_CONST(out, + "<style type=\"text/css\">\n" + "a, a:active {text-decoration: none; color: blue;}\n" + "a:visited {color: #48468F;}\n" + "a:hover, a:focus {text-decoration: underline; color: red;}\n" + "body {background-color: #F5F5F5;}\n" + "h2 {margin-bottom: 12px;}\n" + "table {margin-left: 12px;}\n" + "th, td {" + " font-family: \"Courier New\", Courier, monospace;" + " font-size: 10pt;" + " text-align: left;" + "}\n" + "th {" + " font-weight: bold;" + " padding-right: 14px;" + " padding-bottom: 3px;" + "}\n" + ); + BUFFER_APPEND_STRING_CONST(out, + "td {padding-right: 14px;}\n" + "td.s, th.s {text-align: right;}\n" + "div.list {" + " background-color: white;" + " border-top: 1px solid #646464;" + " border-bottom: 1px solid #646464;" + " padding-top: 10px;" + " padding-bottom: 14px;" + "}\n" + "div.foot {" + " font-family: \"Courier New\", Courier, monospace;" + " font-size: 10pt;" + " color: #787878;" + " padding-top: 4px;" + "}\n" + "</style>\n" + ); + } + + BUFFER_APPEND_STRING_CONST(out, "</head>\n<body>\n"); + + /* HEADER.txt */ + if (p->conf.show_header) { + stream s; + /* if we have a HEADER file, display it in <pre class="header"></pre> */ + + buffer_copy_string_buffer(p->tmp_buf, con->physical.path); + BUFFER_APPEND_SLASH(p->tmp_buf); + BUFFER_APPEND_STRING_CONST(p->tmp_buf, "HEADER.txt"); + + if (-1 != stream_open(&s, p->tmp_buf)) { + BUFFER_APPEND_STRING_CONST(out, "<pre class=\"header\">"); + buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML); + BUFFER_APPEND_STRING_CONST(out, "</pre>"); + } + stream_close(&s); + } + + BUFFER_APPEND_STRING_CONST(out, "<h2>Index of "); + buffer_append_string_encoded(out, CONST_BUF_LEN(con->uri.path), ENCODING_MINIMAL_XML); + BUFFER_APPEND_STRING_CONST(out, + "</h2>\n" + "<div class=\"list\">\n" + "<table cellpadding=\"0\" cellspacing=\"0\">\n" + "<thead>" + "<tr>" + "<th class=\"n\">Name</th>" + "<th class=\"m\">Last Modified</th>" + "<th class=\"s\">Size</th>" + "<th class=\"t\">Type</th>" + "</tr>" + "</thead>\n" + "<tbody>\n" + "<tr>" + "<td class=\"n\"><a href=\"../\">Parent Directory</a>/</td>" + "<td class=\"m\"> </td>" + "<td class=\"s\">- </td>" + "<td class=\"t\">Directory</td>" + "</tr>\n" + ); +} + +static void http_list_directory_footer(server *srv, connection *con, plugin_data *p, buffer *out) { + UNUSED(srv); + + BUFFER_APPEND_STRING_CONST(out, + "</tbody>\n" + "</table>\n" + "</div>\n" + ); + + if (p->conf.show_readme) { + stream s; + /* if we have a README file, display it in <pre class="readme"></pre> */ + + buffer_copy_string_buffer(p->tmp_buf, con->physical.path); + BUFFER_APPEND_SLASH(p->tmp_buf); + BUFFER_APPEND_STRING_CONST(p->tmp_buf, "README.txt"); + + if (-1 != stream_open(&s, p->tmp_buf)) { + BUFFER_APPEND_STRING_CONST(out, "<pre class=\"readme\">"); + buffer_append_string_encoded(out, s.start, s.size, ENCODING_MINIMAL_XML); + BUFFER_APPEND_STRING_CONST(out, "</pre>"); + } + stream_close(&s); + } + + BUFFER_APPEND_STRING_CONST(out, + "<div class=\"foot\">" + ); + + if (buffer_is_empty(con->conf.server_tag)) { + BUFFER_APPEND_STRING_CONST(out, PACKAGE_NAME "/" PACKAGE_VERSION); + } else { + buffer_append_string_buffer(out, con->conf.server_tag); + } + + BUFFER_APPEND_STRING_CONST(out, + "</div>\n" + "</body>\n" + "</html>\n" + ); +} + +static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) { + DIR *dp; + buffer *out; + struct dirent *dent; + struct stat st; + char *path, *path_file; + size_t i; + int hide_dotfiles = p->conf.hide_dot_files; + dirls_list_t dirs, files, *list; + dirls_entry_t *tmp; + char sizebuf[sizeof("999.9K")]; + char datebuf[sizeof("2005-Jan-01 22:23:24")]; + size_t k; + const char *content_type; + long name_max; +#ifdef HAVE_XATTR + char attrval[128]; + int attrlen; +#endif +#ifdef HAVE_LOCALTIME_R + struct tm tm; +#endif + + if (dir->used == 0) return -1; + + i = dir->used - 1; + +#ifdef HAVE_PATHCONF + if (-1 == (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) { +#ifdef NAME_MAX + name_max = NAME_MAX; +#else + name_max = 256; /* stupid default */ +#endif + } +#elif defined __WIN32 + name_max = FILENAME_MAX; +#else + name_max = NAME_MAX; +#endif + + path = malloc(dir->used + name_max); + assert(path); + strcpy(path, dir->ptr); + path_file = path + i; + + if (NULL == (dp = opendir(path))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "opendir failed:", dir, strerror(errno)); + + free(path); + return -1; + } + + dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); + assert(dirs.ent); + dirs.size = DIRLIST_BLOB_SIZE; + dirs.used = 0; + files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); + assert(files.ent); + files.size = DIRLIST_BLOB_SIZE; + files.used = 0; + + while ((dent = readdir(dp)) != NULL) { + unsigned short exclude_match = 0; + + if (dent->d_name[0] == '.') { + if (hide_dotfiles) + continue; + if (dent->d_name[1] == '\0') + continue; + if (dent->d_name[1] == '.' && dent->d_name[2] == '\0') + continue; + } + + if (p->conf.hide_readme_file) { + if (strcmp(dent->d_name, "README.txt") == 0) + continue; + } + if (p->conf.hide_header_file) { + if (strcmp(dent->d_name, "HEADER.txt") == 0) + continue; + } + + /* compare d_name against excludes array + * elements, skipping any that match. + */ +#ifdef HAVE_PCRE_H + for(i = 0; i < p->conf.excludes->used; i++) { + int n; +#define N 10 + int ovec[N * 3]; + pcre *regex = p->conf.excludes->ptr[i]->regex; + + if ((n = pcre_exec(regex, NULL, dent->d_name, + strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) { + if (n != PCRE_ERROR_NOMATCH) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "execution error while matching:", n); + + return -1; + } + } + else { + exclude_match = 1; + break; + } + } + + if (exclude_match) { + continue; + } +#endif + + i = strlen(dent->d_name); + + /* NOTE: the manual says, d_name is never more than NAME_MAX + * so this should actually not be a buffer-overflow-risk + */ + if (i > (size_t)name_max) continue; + + memcpy(path_file, dent->d_name, i + 1); + if (stat(path, &st) != 0) + continue; + + list = &files; + if (S_ISDIR(st.st_mode)) + list = &dirs; + + if (list->used == list->size) { + list->size += DIRLIST_BLOB_SIZE; + list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size); + assert(list->ent); + } + + tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i); + tmp->mtime = st.st_mtime; + tmp->size = st.st_size; + tmp->namelen = i; + memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1); + + list->ent[list->used++] = tmp; + } + closedir(dp); + + if (dirs.used) http_dirls_sort(dirs.ent, dirs.used); + + if (files.used) http_dirls_sort(files.ent, files.used); + + out = chunkqueue_get_append_buffer(con->write_queue); + BUFFER_COPY_STRING_CONST(out, "<?xml version=\"1.0\" encoding=\""); + if (buffer_is_empty(p->conf.encoding)) { + BUFFER_APPEND_STRING_CONST(out, "iso-8859-1"); + } else { + buffer_append_string_buffer(out, p->conf.encoding); + } + BUFFER_APPEND_STRING_CONST(out, "\"?>\n"); + http_list_directory_header(srv, con, p, out); + + /* directories */ + for (i = 0; i < dirs.used; i++) { + tmp = dirs.ent[i]; + +#ifdef HAVE_LOCALTIME_R + localtime_r(&(tmp->mtime), &tm); + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); +#else + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); +#endif + + BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); + buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); + BUFFER_APPEND_STRING_CONST(out, "/\">"); + buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); + BUFFER_APPEND_STRING_CONST(out, "</a>/</td><td class=\"m\">"); + buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); + BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n"); + + free(tmp); + } + + /* files */ + for (i = 0; i < files.used; i++) { + tmp = files.ent[i]; + + content_type = NULL; +#ifdef HAVE_XATTR + + if (con->conf.use_xattr) { + memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); + attrlen = sizeof(attrval) - 1; + if (attr_get(path, "Content-Type", attrval, &attrlen, 0) == 0) { + attrval[attrlen] = '\0'; + content_type = attrval; + } + } +#endif + + if (content_type == NULL) { + content_type = "application/octet-stream"; + 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 (tmp->namelen < ct_len) + continue; + + if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) { + content_type = ds->value->ptr; + break; + } + } + } + +#ifdef HAVE_LOCALTIME_R + localtime_r(&(tmp->mtime), &tm); + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); +#else + strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); +#endif + http_list_directory_sizefmt(sizebuf, tmp->size); + + BUFFER_APPEND_STRING_CONST(out, "<tr><td class=\"n\"><a href=\""); + buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); + BUFFER_APPEND_STRING_CONST(out, "\">"); + buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); + BUFFER_APPEND_STRING_CONST(out, "</a></td><td class=\"m\">"); + buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); + BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"s\">"); + buffer_append_string(out, sizebuf); + BUFFER_APPEND_STRING_CONST(out, "</td><td class=\"t\">"); + buffer_append_string(out, content_type); + BUFFER_APPEND_STRING_CONST(out, "</td></tr>\n"); + + free(tmp); + } + + free(files.ent); + free(dirs.ent); + free(path); + + http_list_directory_footer(srv, con, p, out); + + /* Insert possible charset to Content-Type */ + if (buffer_is_empty(p->conf.encoding)) { + response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + } else { + buffer_copy_string(p->content_charset, "text/html; charset="); + buffer_append_string_buffer(p->content_charset, p->conf.encoding); + response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset)); + } + + con->file_finished = 1; + + return 0; +} + + + +URIHANDLER_FUNC(mod_dirlisting_subrequest) { + plugin_data *p = p_d; + stat_cache_entry *sce = NULL; + + UNUSED(srv); + + if (con->physical.path->used == 0) return HANDLER_GO_ON; + if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON; + + mod_dirlisting_patch_connection(srv, con, p); + + if (!p->conf.dir_listing) return HANDLER_GO_ON; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Dir-Listing"); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path); + } + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + fprintf(stderr, "%s.%d: %s\n", __FILE__, __LINE__, con->physical.path->ptr); + SEGFAULT(); + } + + if (!S_ISDIR(sce->st.st_mode)) return HANDLER_GO_ON; + + if (http_list_directory(srv, con, p, con->physical.path)) { + /* dirlisting failed */ + con->http_status = 403; + } + + buffer_reset(con->physical.path); + + /* not found */ + return HANDLER_FINISHED; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_dirlisting_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("dirlisting"); + + p->init = mod_dirlisting_init; + p->handle_subrequest_start = mod_dirlisting_subrequest; + p->set_defaults = mod_dirlisting_set_defaults; + p->cleanup = mod_dirlisting_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_evasive.c b/src/mod_evasive.c new file mode 100644 index 0000000..b9d19ca --- /dev/null +++ b/src/mod_evasive.c @@ -0,0 +1,178 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "inet_ntop_cache.h" + +/** + * mod_evasive + * + * we indent to implement all features the mod_evasive from apache has + * + * - limit of connections per IP + * - provide a list of block-listed ip/networks (no access) + * - provide a white-list of ips/network which is not affected by the limit + * (hmm, conditionals might be enough) + * - provide a bandwidth limiter per IP + * + * started by: + * - w1zzard@techpowerup.com + */ + +typedef struct { + unsigned short max_conns; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_evasive_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +FREE_FUNC(mod_evasive_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]; + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_evasive_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "evasive.max-conns-per-ip", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, + { 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; + + s = calloc(1, sizeof(plugin_config)); + s->max_conns = 0; + + cv[0].destination = &(s->max_conns); + + 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_evasive_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(max_conns); + + /* 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]; + s = p->config_storage[i]; + + /* 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("evasive.max-conns-per-ip"))) { + PATCH(max_conns); + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_evasive_uri_handler) { + plugin_data *p = p_d; + size_t conns_by_ip = 0; + size_t j; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_evasive_patch_connection(srv, con, p); + + /* no limit set, nothing to block */ + if (p->conf.max_conns == 0) return HANDLER_GO_ON; + + for (j = 0; j < srv->conns->used; j++) { + connection *c = srv->conns->ptr[j]; + + /* check if other connections are already actively serving data for the same IP + * we can only ban connections which are already behind the 'read request' state + * */ + if (c->dst_addr.ipv4.sin_addr.s_addr == con->dst_addr.ipv4.sin_addr.s_addr && + c->state > CON_STATE_REQUEST_END) { + conns_by_ip++; + + if (conns_by_ip > p->conf.max_conns) { + log_error_write(srv, __FILE__, __LINE__, "ss", + inet_ntop_cache_get_ip(srv, &(con->dst_addr)), + "turned away. Too many connections."); + + con->http_status = 403; + return HANDLER_FINISHED; + } + } + } + + return HANDLER_GO_ON; +} + + +int mod_evasive_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("evasive"); + + p->init = mod_evasive_init; + p->set_defaults = mod_evasive_set_defaults; + p->handle_uri_clean = mod_evasive_uri_handler; + p->cleanup = mod_evasive_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_evhost.c b/src/mod_evhost.c new file mode 100644 index 0000000..bc8adb6 --- /dev/null +++ b/src/mod_evhost.c @@ -0,0 +1,334 @@ +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include "plugin.h" +#include "log.h" +#include "response.h" +#include "stat_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) continue; + + if(s->path_pieces) { + size_t j; + for (j = 0; j < s->len; j++) { + buffer_free(s->path_pieces[j]); + } + + 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 = calloc(1, 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(path_pieces); + PATCH(len); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#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; + stat_cache_entry *sce = NULL; + + /* not authority set */ + if (con->uri.authority->used == 0) return HANDLER_GO_ON; + + mod_evhost_patch_connection(srv, con, p); + + /* missing even default(global) conf */ + if (0 == p->conf.len) { + return HANDLER_GO_ON; + } + + 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_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf); + not_good = 1; + } else if(!S_ISDIR(sce->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 0000000..619b542 --- /dev/null +++ b/src/mod_expire.c @@ -0,0 +1,361 @@ +#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" +#include "stat_cache.h" + +/** + * 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 = 1; + 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 = calloc(1, 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->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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(expire_url); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +URIHANDLER_FUNC(mod_expire_path_handler) { + plugin_data *p = p_d; + int s_len; + size_t k; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_expire_patch_connection(srv, con, p); + + 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; + stat_cache_entry *sce = NULL; + + stat_cache_get_entry(srv, con, con->physical.path, &sce); + + switch(mod_expire_get_offset(srv, p, ds->value, &ts)) { + case 0: + /* access */ + t = (ts + srv->cur_ts); + break; + case 1: + /* modification */ + + t = (ts + sce->st.st_mtime); + break; + default: + /* -1 is handled at parse-time */ + break; + } + + + 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; + + /* HTTP/1.0 */ + response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_BUF_LEN(p->expire_tstmp)); + + /* HTTP/1.1 */ + buffer_copy_string(p->expire_tstmp, "max-age="); + buffer_append_long(p->expire_tstmp, ts); + + response_header_overwrite(srv, con, CONST_STR_LEN("Cache-Control"), 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_subrequest_start = mod_expire_path_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 0000000..da20c76 --- /dev/null +++ b/src/mod_fastcgi.c @@ -0,0 +1,3874 @@ +#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 "stat_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 disabled_until; /* this proc is disabled until, use something else until than */ + + 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 { + /* the key that is used to reference this value */ + buffer *id; + + /* 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; + + /* + * append PATH_INFO to SCRIPT_FILENAME + * + * php needs this if cgi.fix_pathinfo is provied + * + */ + + unsigned short break_scriptfilename_for_php; + + /* + * If the backend includes X-LIGHTTPD-send-file in the response + * we use the value as filename and ignore the content. + * + */ + unsigned short allow_xsendfile; + + 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 */ + + buffer *strip_request_uri; +} 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; + + array *ext_mapping; + + 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; + + buffer *statuskey; + + 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_UNSET, + FCGI_STATE_INIT, + FCGI_STATE_CONNECT_DELAYED, + FCGI_STATE_PREPARE_WRITE, + FCGI_STATE_WRITE, + FCGI_STATE_READ +} fcgi_connection_state_t; + +typedef struct { + fcgi_proc *proc; + fcgi_extension_host *host; + fcgi_extension *ext; + + fcgi_connection_state_t state; + time_t state_timestamp; + + int reconnects; /* number of reconnect attempts */ + + chunkqueue *rb; /* read queue */ + chunkqueue *wb; /* write queue */ + + buffer *response_header; + + size_t request_id; + int fd; /* fd to the fastcgi process */ + int fde_ndx; /* index into the fd-event buffer */ + + pid_t pid; + int got_proc; + + int send_content_body; + + 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); + +data_integer *status_counter_get_counter(server *srv, const char *s, size_t len) { + data_integer *di; + + if (NULL == (di = (data_integer *)array_get_element(srv->status, s))) { + /* not found, create it */ + + if (NULL == (di = (data_integer *)array_get_unused_element(srv->status, TYPE_INTEGER))) { + di = data_integer_init(); + } + buffer_copy_string_len(di->key, s, len); + di->value = 0; + + array_insert_unique(srv->status, (data_unset *)di); + } + return di; +} + +/* dummies of the statistic framework functions + * they will be moved to a statistics.c later */ +int status_counter_inc(server *srv, const char *s, size_t len) { + data_integer *di = status_counter_get_counter(srv, s, len); + + di->value++; + + return 0; +} + +int status_counter_dec(server *srv, const char *s, size_t len) { + data_integer *di = status_counter_get_counter(srv, s, len); + + if (di->value > 0) di->value--; + + return 0; +} + +int status_counter_set(server *srv, const char *s, size_t len, int val) { + data_integer *di = status_counter_get_counter(srv, s, len); + + di->value = val; + + return 0; +} + +int fastcgi_status_copy_procname(buffer *b, fcgi_extension_host *host, fcgi_proc *proc) { + buffer_copy_string(b, "fastcgi.backend."); + buffer_append_string_buffer(b, host->id); + buffer_append_string(b, "."); + buffer_append_long(b, proc->id); + + return 0; +} + +int fastcgi_status_init(server *srv, buffer *b, fcgi_extension_host *host, fcgi_proc *proc) { +#define CLEAN(x) \ + fastcgi_status_copy_procname(b, host, proc); \ + buffer_append_string(b, x); \ + status_counter_set(srv, CONST_BUF_LEN(b), 0); + + CLEAN(".disabled"); + CLEAN(".died"); + CLEAN(".overloaded"); + CLEAN(".connected"); + CLEAN(".load"); + +#undef CLEAN + + return 0; +} + +static handler_ctx * handler_ctx_init() { + handler_ctx * hctx; + + hctx = calloc(1, sizeof(*hctx)); + assert(hctx); + + hctx->fde_ndx = -1; + + hctx->response_header = buffer_init(); + + hctx->request_id = 0; + hctx->state = FCGI_STATE_INIT; + hctx->proc = NULL; + + hctx->fd = -1; + + hctx->reconnects = 0; + hctx->send_content_body = 1; + + hctx->rb = chunkqueue_init(); + hctx->wb = chunkqueue_init(); + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + if (hctx->host) { + hctx->host->load--; + hctx->host = NULL; + } + + buffer_free(hctx->response_header); + + chunkqueue_free(hctx->rb); + chunkqueue_free(hctx->wb); + + 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->id = buffer_init(); + 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(); + f->strip_request_uri = buffer_init(); + + return f; +} + +void fastcgi_host_free(fcgi_extension_host *h) { + if (!h) return; + + buffer_free(h->id); + buffer_free(h->host); + buffer_free(h->unixsocket); + buffer_free(h->docroot); + buffer_free(h->bin_path); + buffer_free(h->strip_request_uri); + 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(); + + p->statuskey = 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); + buffer_free(p->statuskey); + + 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); + array_free(s->ext_mapping); + + 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 + 1) { + env->size += 16; + env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr)); + } + + env->ptr[env->used++] = dst; + + return 0; +} + +static int parse_binpath(char_array *env, buffer *b) { + char *start; + size_t i; + /* search for spaces */ + + start = b->ptr; + for (i = 0; i < b->used - 1; i++) { + switch(b->ptr[i]) { + case ' ': + case '\t': + /* a WS, stop here and copy the argument */ + + 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)); + } + + b->ptr[i] = '\0'; + + env->ptr[env->used++] = start; + + start = b->ptr + i + 1; + break; + default: + break; + } + } + + if (env->size == 0) { + env->size = 16; + env->ptr = malloc(env->size * sizeof(*env->ptr)); + } else if (env->size == env->used) { /* we need one extra for the terminating NULL */ + env->size += 16; + env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr)); + } + + /* the rest */ + env->ptr[env->used++] = start; + + if (env->size == 0) { + env->size = 16; + env->ptr = malloc(env->size * sizeof(*env->ptr)); + } else if (env->size == env->used) { /* we need one extra for the terminating NULL */ + env->size += 16; + env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr)); + } + + /* terminate */ + env->ptr[env->used++] = NULL; + + 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->used - 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__, + "sdb", "gethostbyname failed: ", + 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__, "sbds", + "bind failed for:", + proc->socket, + proc->port, + 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: { + size_t i = 0; + char *c; + char_array env; + char_array arg; + + /* create environment */ + env.ptr = NULL; + env.size = 0; + env.used = 0; + + arg.ptr = NULL; + arg.size = 0; + arg.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; + + parse_binpath(&arg, host->bin_path); + + /* chdir into the base of the bin-path, + * search for the last / */ + if (NULL != (c = strrchr(arg.ptr[0], '/'))) { + *c = '\0'; + + /* change to the physical directory */ + if (-1 == chdir(arg.ptr[0])) { + *c = '/'; + log_error_write(srv, __FILE__, __LINE__, "sss", "chdir failed:", strerror(errno), arg.ptr[0]); + } + *c = '/'; + } + + + /* exec the cgi */ + execve(arg.ptr[0], arg.ptr, env.ptr); + + log_error_write(srv, __FILE__, __LINE__, "sbs", + "execve 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: + log_error_write(srv, __FILE__, __LINE__, "sbs", + "the fastcgi-backend", host->bin_path, "failed to start:"); + /* the child should not terminate at all */ + if (WIFEXITED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sdb", + "child exited with status", + WEXITSTATUS(status), host->bin_path); + log_error_write(srv, __FILE__, __LINE__, "s", + "if you try do run PHP as FastCGI backend make sure you use the FastCGI enabled version.\n" + "You can find out if it is the right one by executing 'php -v' and it should display '(cgi-fcgi)' " + "in the output, NOT (cgi) NOR (cli)\n" + "For more information check http://www.lighttpd.net/documentation/fastcgi.html#preparing-php-as-a-fastcgi-program"); + log_error_write(srv, __FILE__, __LINE__, "s", + "If this is PHP on Gentoo add fastcgi to the USE flags"); + } else if (WIFSIGNALED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "terminated by signal:", + WTERMSIG(status)); + + if (WTERMSIG(status) == 11) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "to be exact: it seg-fault, crashed, died, ... you get the idea." ); + log_error_write(srv, __FILE__, __LINE__, "s", + "If this is PHP try to remove the byte-code caches for now and try again."); + } + } 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 */ + { "fastcgi.map-extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { 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; + s->ext_mapping = array_init(); + + cv[0].destination = s->exts; + cv[1].destination = &(s->debug); + cv[2].destination = s->ext_mapping; + + 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 *host; + + 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-not-working", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 this is broken for now */ + { "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 */ + + { "broken-scriptfilename", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ + { "allow-x-send-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ + { "strip-request-uri", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + + { 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; + } + + host = fastcgi_host_init(); + + buffer_copy_string_buffer(host->id, da_host->key); + + host->check_local = 1; + host->min_procs = 4; + host->max_procs = 4; + host->max_load_per_proc = 1; + host->idle_timeout = 60; + host->mode = FCGI_RESPONDER; + host->disable_time = 60; + host->break_scriptfilename_for_php = 0; + host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */ + + fcv[0].destination = host->host; + fcv[1].destination = host->docroot; + fcv[2].destination = fcgi_mode; + fcv[3].destination = host->unixsocket; + fcv[4].destination = host->bin_path; + + fcv[5].destination = &(host->check_local); + fcv[6].destination = &(host->port); + fcv[7].destination = &(host->min_procs); + fcv[8].destination = &(host->max_procs); + fcv[9].destination = &(host->max_load_per_proc); + fcv[10].destination = &(host->idle_timeout); + fcv[11].destination = &(host->disable_time); + + fcv[12].destination = host->bin_env; + fcv[13].destination = host->bin_env_copy; + fcv[14].destination = &(host->break_scriptfilename_for_php); + fcv[15].destination = &(host->allow_xsendfile); + fcv[16].destination = host->strip_request_uri; + + if (0 != config_insert_values_internal(srv, da_host->value, fcv)) { + return HANDLER_ERROR; + } + + if ((!buffer_is_empty(host->host) || host->port) && + !buffer_is_empty(host->unixsocket)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "either host+port or socket"); + + return HANDLER_ERROR; + } + + if (!buffer_is_empty(host->unixsocket)) { + /* unix domain socket */ + + if (host->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(host->host) && + buffer_is_empty(host->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 (host->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(host->bin_path)) { + /* a local socket + self spawning */ + size_t pno; + + /* HACK: just to make sure the adaptive spawing is disabled */ + host->min_procs = host->max_procs; + + if (host->min_procs > host->max_procs) host->max_procs = host->min_procs; + if (host->max_load_per_proc < 1) host->max_load_per_proc = 0; + + if (s->debug) { + log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd", + "--- fastcgi spawning local", + "\n\tproc:", host->bin_path, + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tmin-procs:", host->min_procs, + "\n\tmax-procs:", host->max_procs); + } + + for (pno = 0; pno < host->min_procs; pno++) { + fcgi_proc *proc; + + proc = fastcgi_process_init(); + proc->id = host->num_procs++; + host->max_id++; + + if (buffer_is_empty(host->unixsocket)) { + proc->port = host->port + pno; + } else { + buffer_copy_string_buffer(proc->socket, host->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:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tcurrent:", pno, "/", host->min_procs); + } + + if (fcgi_spawn_connection(srv, p, host, proc)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "[ERROR]: spawning fcgi failed."); + return HANDLER_ERROR; + } + + fastcgi_status_init(srv, p->statuskey, host, proc); + + proc->next = host->first; + if (host->first) host->first->prev = proc; + + host->first = proc; + } + } else { + fcgi_proc *proc; + + proc = fastcgi_process_init(); + proc->id = host->num_procs++; + host->max_id++; + host->active_procs++; + proc->state = PROC_STATE_RUNNING; + + if (buffer_is_empty(host->unixsocket)) { + proc->port = host->port; + } else { + buffer_copy_string_buffer(proc->socket, host->unixsocket); + } + + fastcgi_status_init(srv, p->statuskey, host, proc); + + host->first = proc; + + host->min_procs = 1; + host->max_procs = 1; + } + + if (!buffer_is_empty(fcgi_mode)) { + if (strcmp(fcgi_mode->ptr, "responder") == 0) { + host->mode = FCGI_RESPONDER; + } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) { + host->mode = FCGI_AUTHORIZER; + if (buffer_is_empty(host->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, host); + } + } + } + } + + 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_close(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) { + if (hctx->got_proc) { + /* after the connect the process gets a load */ + hctx->proc->load--; + + status_counter_dec(srv, CONST_STR_LEN("fastcgi.active-requests")); + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".load"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 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 + * + */ + + 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--; + } + + 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); + } + + if (hctx->proc) { + hctx->proc->load--; + fcgi_proclist_sort_down(srv, hctx->host, hctx->proc); + } + + /* perhaps another host gives us more luck */ + hctx->host->load--; + hctx->host = NULL; + + return 0; +} + + +static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + fcgi_connection_close(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 + */ + +typedef enum { + CONNECTION_UNSET, + CONNECTION_OK, + CONNECTION_DELAYED, /* retry after event, take same host */ + CONNECTION_OVERLOADED, /* disable for 1 seconds, take another backend */ + CONNECTION_DEAD /* disable for 60 seconds, take another backend */ +} connection_result_t; + +static connection_result_t 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 (0 == inet_aton(host->host->ptr, &(fcgi_addr_in.sin_addr))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "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__, "sb", + "connect delayed, will continue later:", host->host); + } + + return CONNECTION_DELAYED; + } else if (errno == EAGAIN) { + if (hctx->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "This means that the you have more incoming requests than your fastcgi-backend can handle in parallel. " + "Perhaps it helps to spawn more fastcgi backend or php-children, if not decrease server.max-connections." + "The load for this fastcgi backend is:", proc->load); + } + + return CONNECTION_OVERLOADED; + } else { + log_error_write(srv, __FILE__, __LINE__, "ssdb", + "connect failed:", + strerror(errno), + proc->port, proc->socket); + + return CONNECTION_DEAD; + } + } + + hctx->reconnects = 0; + if (hctx->conf.debug > 1) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect succeeded: ", fcgi_fd); + } + + return CONNECTION_OK; +} + +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++) { + char c = '_'; + if (light_isalpha(ds->key->ptr[j])) { + /* upper-case */ + c = ds->key->ptr[j] & ~32; + } else if (light_isdigit(ds->key->ptr[j])) { + /* copy */ + c = ds->key->ptr[j]; + } + srv->tmp_buf->ptr[srv->tmp_buf->used++] = c; + } + 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++) { + char c = '_'; + if (light_isalpha(ds->key->ptr[j])) { + /* upper-case */ + c = ds->key->ptr[j] & ~32; + } else if (light_isdigit(ds->key->ptr[j])) { + /* copy */ + c = ds->key->ptr[j]; + } + srv->tmp_buf->ptr[srv->tmp_buf->used++] = c; + } + 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; + buffer *b; + + char buf[32]; + 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; + + sock_addr our_addr; + socklen_t our_addr_len; + + /* 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)); + + b = chunkqueue_get_append_buffer(hctx->wb); + + buffer_copy_memory(b, (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)); + + /* get the server-side of the connection to the client */ + our_addr_len = sizeof(our_addr); + + if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) { + s = inet_ntop_cache_get_ip(srv, &(srv_sock->addr)); + } else { + s = inet_ntop_cache_get_ip(srv, &(our_addr)); + } + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)); + + ltostr(buf, +#ifdef HAVE_IPV6 + ntohs(con->dst_addr.plain.sa_family ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port) +#else + ntohs(con->dst_addr.ipv4.sin_port) +#endif + ); + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_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. + */ + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); + + if (!buffer_is_empty(con->request.pathinfo)) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); + + /* PATH_TRANSLATED is only defined if PATH_INFO is set */ + + if (!buffer_is_empty(host->docroot)) { + buffer_copy_string_buffer(p->path, host->docroot); + } else { + buffer_copy_string_buffer(p->path, con->physical.doc_root); + } + buffer_append_string_buffer(p->path, con->request.pathinfo); + 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_INFO"), CONST_STR_LEN("")); + } + } + + /* + * 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 { + buffer_copy_string_buffer(p->path, con->physical.path); + + /* cgi.fix_pathinfo need a broken SCRIPT_FILENAME to find out what PATH_INFO is itself + * + * see src/sapi/cgi_main.c, init_request_info() + */ + if (host->break_scriptfilename_for_php) { + buffer_append_string_buffer(p->path, con->request.pathinfo); + } + + 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(con->physical.doc_root)); + } + + if (host->strip_request_uri->used > 1) { + /* we need at least one char to strip off */ + /** + * /app1/index/list + * + * stripping /app1 or /app1/ should lead to + * + * /index/list + * + */ + if ('/' != host->strip_request_uri->ptr[host->strip_request_uri->used - 2]) { + /* fix the user-input to have / as last char */ + buffer_append_string(host->strip_request_uri, "/"); + } + + if (con->request.orig_uri->used >= host->strip_request_uri->used && + 0 == strncmp(con->request.orig_uri->ptr, host->strip_request_uri->ptr, host->strip_request_uri->used - 1)) { + /* the left is the same */ + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), + con->request.orig_uri->ptr + (host->strip_request_uri->used - 2), + con->request.orig_uri->used - (host->strip_request_uri->used - 2)); + } else { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); + } + } else { + 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(b, (const char *)&header, sizeof(header)); + buffer_append_memory(b, (const char *)p->fcgi_env->ptr, p->fcgi_env->used); + + fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0); + buffer_append_memory(b, (const char *)&header, sizeof(header)); + + b->used++; /* add virtual \0 */ + hctx->wb->bytes_in += b->used - 1; + + if (con->request.content_length) { + chunkqueue *req_cq = con->request_content_queue; + chunk *req_c; + off_t offset; + + /* something to send ? */ + for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; ) { + off_t weWant = req_cq->bytes_in - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cq->bytes_in - offset; + off_t written = 0; + off_t weHave = 0; + + /* we announce toWrite octects + * now take all the request_content chunk that we need to fill this request + * */ + + b = chunkqueue_get_append_buffer(hctx->wb); + fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0); + buffer_copy_memory(b, (const char *)&header, sizeof(header)); + hctx->wb->bytes_in += sizeof(header); + + if (p->conf.debug > 10) { + fprintf(stderr, "%s.%d: tosend: %lld / %lld\n", __FILE__, __LINE__, offset, req_cq->bytes_in); + } + + for (written = 0; written != weWant; ) { + if (p->conf.debug > 10) { + fprintf(stderr, "%s.%d: chunk: %lld / %lld\n", __FILE__, __LINE__, written, weWant); + } + + switch (req_c->type) { + case FILE_CHUNK: + weHave = req_c->file.length - req_c->offset; + + if (weHave > weWant - written) weHave = weWant - written; + + if (p->conf.debug > 10) { + fprintf(stderr, "%s.%d: sending %lld bytes from (%lld / %lld) %s\n", + __FILE__, __LINE__, + weHave, + req_c->offset, + req_c->file.length, + req_c->file.name->ptr); + } + + assert(weHave != 0); + + chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave); + + req_c->offset += weHave; + req_cq->bytes_out += weHave; + written += weHave; + + hctx->wb->bytes_in += weHave; + + /* steal the tempfile + * + * This is tricky: + * - we reference the tempfile from the request-content-queue several times + * if the req_c is larger than FCGI_MAX_LENGTH + * - we can't simply cleanup the request-content-queue as soon as possible + * as it would remove the tempfiles + * - the idea is to 'steal' the tempfiles and attach the is_temp flag to the last + * referencing chunk of the fastcgi-write-queue + * + * */ + + if (req_c->offset == req_c->file.length) { + chunk *c; + + if (p->conf.debug > 10) { + fprintf(stderr, "%s.%d: next chunk\n", __FILE__, __LINE__); + } + c = hctx->wb->last; + + assert(c->type == FILE_CHUNK); + assert(req_c->file.is_temp == 1); + + c->file.is_temp = 1; + req_c->file.is_temp = 0; + + chunkqueue_remove_finished_chunks(req_cq); + + req_c = req_cq->first; + } + + break; + case MEM_CHUNK: + /* append to the buffer */ + weHave = req_c->mem->used - 1 - req_c->offset; + + if (weHave > weWant - written) weHave = weWant - written; + + buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave); + + req_c->offset += weHave; + req_cq->bytes_out += weHave; + written += weHave; + + hctx->wb->bytes_in += weHave; + + if (req_c->offset == req_c->mem->used - 1) { + chunkqueue_remove_finished_chunks(req_cq); + + req_c = req_cq->first; + } + + break; + default: + break; + } + } + + b->used++; /* add virtual \0 */ + offset += weWant; + } + } + + b = chunkqueue_get_append_buffer(hctx->wb); + /* terminate STDIN */ + fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); + buffer_copy_memory(b, (const char *)&header, sizeof(header)); + b->used++; /* add virtual \0 */ + + hctx->wb->bytes_in += 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); + + buffer_copy_string_buffer(p->parse_response, in); + + /* search for \n */ + for (s = p->parse_response->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) { + char *key, *value; + int key_len; + data_string *ds; + + /* a good day. Someone has read the specs and is sending a \r\n to us */ + + if (ns > p->parse_response->ptr && + *(ns-1) == '\r') { + *(ns-1) = '\0'; + } + + ns[0] = '\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 */ + + /* don't forward Status: */ + if (0 != strncasecmp(key, "Status", key_len)) { + 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; +} + +typedef struct { + buffer *b; + size_t len; + int type; + int padding; + size_t request_id; +} fastcgi_response_packet; + +static int fastcgi_get_packet(server *srv, handler_ctx *hctx, fastcgi_response_packet *packet) { + chunk * c; + size_t offset = 0; + size_t toread = 0; + FCGI_Header *header; + + if (!hctx->rb->first) return -1; + + packet->b = buffer_init(); + packet->len = 0; + packet->type = 0; + packet->padding = 0; + packet->request_id = 0; + + /* get at least the FastCGI header */ + for (c = hctx->rb->first; c; c = c->next) { + if (packet->b->used == 0) { + buffer_copy_string_len(packet->b, c->mem->ptr + c->offset, c->mem->used - c->offset - 1); + } else { + buffer_append_string_len(packet->b, c->mem->ptr + c->offset, c->mem->used - c->offset - 1); + } + + if (packet->b->used >= sizeof(*header) + 1) break; + } + + if ((packet->b->used == 0) || + (packet->b->used - 1 < sizeof(FCGI_Header))) { + /* no header */ + buffer_free(packet->b); + + log_error_write(srv, __FILE__, __LINE__, "s", "FastCGI: header too small"); + return -1; + } + + /* we have at least a header, now check how much me have to fetch */ + header = (FCGI_Header *)(packet->b->ptr); + + packet->len = (header->contentLengthB0 | (header->contentLengthB1 << 8)) + header->paddingLength; + packet->request_id = (header->requestIdB0 | (header->requestIdB1 << 8)); + packet->type = header->type; + packet->padding = header->paddingLength; + + /* the first bytes in packet->b are the header */ + offset = sizeof(*header); + + /* ->b should only be the content */ + buffer_copy_string(packet->b, ""); /* used == 1 */ + + if (packet->len) { + /* copy the content */ + for (; c && (packet->b->used < packet->len + 1); c = c->next) { + size_t weWant = packet->len - (packet->b->used - 1); + size_t weHave = c->mem->used - c->offset - offset - 1; + + if (weHave > weWant) weHave = weWant; + + buffer_append_string_len(packet->b, c->mem->ptr + c->offset + offset, weHave); + + /* we only skipped the first 8 bytes as they are the fcgi header */ + offset = 0; + } + + if (packet->b->used < packet->len + 1) { + /* we didn't got the full packet */ + + buffer_free(packet->b); + return -1; + } + + packet->b->used -= packet->padding; + packet->b->ptr[packet->b->used - 1] = '\0'; + } + + /* tag the chunks as read */ + toread = packet->len + sizeof(FCGI_Header); + for (c = hctx->rb->first; c && toread; c = c->next) { + if (c->mem->used - c->offset - 1 <= toread) { + /* we read this whole buffer, move it to unused */ + toread -= c->mem->used - c->offset - 1; + c->offset = c->mem->used - 1; /* everthing has been written */ + } else { + c->offset += toread; + toread = 0; + } + } + + chunkqueue_remove_finished_chunks(hctx->rb); + + return 0; +} + +static int fcgi_demux_response(server *srv, handler_ctx *hctx) { + int fin = 0; + int toread; + 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, &toread)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "unexpected end-of-file (perhaps the fastcgi process died):", + fcgi_fd); + return -1; + } + + /* init read-buffer */ + + if (toread > 0) { + buffer *b; + + b = chunkqueue_get_append_buffer(hctx->rb); + buffer_prepare_copy(b, toread + 1); + + /* append to read-buffer */ + if (-1 == (r = read(hctx->fd, b->ptr, toread))) { + 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); + + b->used = r + 1; /* one extra for the fake \0 */ + b->ptr[b->used - 1] = '\0'; + } else { + log_error_write(srv, __FILE__, __LINE__, "ssdsbsbsd", + "unexpected end-of-file (perhaps the fastcgi process died):", + "pid:", proc->pid, + "socket:", proc->socket, + "host:", host->host, + "port:", proc->port); + + return -1; + } + + /* + * parse the fastcgi packets and forward the content to the write-queue + * + */ + while (fin == 0) { + fastcgi_response_packet packet; + + /* check if we have at least one packet */ + if (0 != fastcgi_get_packet(srv, hctx, &packet)) { + /* no full packet */ + break; + } + + switch(packet.type) { + case FCGI_STDOUT: + if (packet.len == 0) break; + + /* is the header already finished */ + if (0 == con->file_started) { + char *c; + size_t blen; + data_string *ds; + + /* search for header terminator + * + * if we start with \r\n check if last packet terminated with \r\n + * if we start with \n check if last packet terminated with \n + * search for \r\n\r\n + * search for \n\n + */ + + if (hctx->response_header->used == 0) { + buffer_copy_string_buffer(hctx->response_header, packet.b); + } else { + buffer_append_string_buffer(hctx->response_header, packet.b); + } + + if (NULL != (c = buffer_search_string_len(hctx->response_header, CONST_STR_LEN("\r\n\r\n")))) { + blen = hctx->response_header->used - (c - hctx->response_header->ptr) - 4; + hctx->response_header->used = (c - hctx->response_header->ptr) + 3; + c += 4; /* point the the start of the response */ + } else if (NULL != (c = buffer_search_string_len(hctx->response_header, CONST_STR_LEN("\n\n")))) { + blen = hctx->response_header->used - (c - hctx->response_header->ptr) - 2; + hctx->response_header->used = c - hctx->response_header->ptr + 2; + c += 2; /* point the the start of the response */ + } else { + /* no luck, no header found */ + break; + } + + /* parse the response header */ + fcgi_response_parse(srv, con, p, hctx->response_header); + + con->file_started = 1; + + if (host->mode == FCGI_AUTHORIZER && + (con->http_status == 0 || + con->http_status == 200)) { + /* a authorizer with approved the static request, ignore the content here */ + hctx->send_content_body = 0; + } + + if (host->allow_xsendfile && + NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-LIGHTTPD-send-file"))) { + stat_cache_entry *sce; + + if (HANDLER_ERROR != stat_cache_get_entry(srv, con, ds->value, &sce)) { + /* found */ + + http_chunk_append_file(srv, con, ds->value, 0, sce->st.st_size); + hctx->send_content_body = 0; /* ignore the content */ + joblist_append(srv, con); + } + } + + + if (hctx->send_content_body && blen > 1) { + /* 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; + } + + http_chunk_append_mem(srv, con, c, blen); + joblist_append(srv, con); + } + } else if (hctx->send_content_body && packet.b->used > 1) { + if (con->request.http_version == HTTP_VERSION_1_1 && + !(con->parsed_response & HTTP_CONTENT_LENGTH)) { + /* enable chunked-transfer-encoding */ + con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + } + + http_chunk_append_mem(srv, con, packet.b->ptr, packet.b->used); + joblist_append(srv, con); + } + break; + case FCGI_STDERR: + log_error_write(srv, __FILE__, __LINE__, "sb", + "FastCGI-stderr:", packet.b); + + 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: ", packet.type); + break; + } + buffer_free(packet.b); + } + + 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 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 (!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->disabled_until)) { + proc->state = PROC_STATE_RUNNING; + host->active_procs++; + + fastcgi_status_copy_procname(p->statuskey, host, proc); + buffer_append_string(p->statuskey, ".disabled"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 0); + + 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)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdsd", + "child exited, pid:", proc->pid, + "status:", WEXITSTATUS(status)); +#endif + } 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 (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", + "--- fastcgi spawning", + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tcurrent:", 1, "/", host->min_procs); + } + + 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; +} + +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 ret; + + /* sanity check */ + if (!host || + ((!host->host->used || !host->port) && !host->unixsocket->used)) { + log_error_write(srv, __FILE__, __LINE__, "sxddd", + "write-req: error", + host, + host->host->used, + host->port, + host->unixsocket->used); + return HANDLER_ERROR; + } + + /* we can't handle this in the switch as we have to fall through in it */ + if (hctx->state == FCGI_STATE_CONNECT_DELAYED) { + 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) { + if (!hctx->proc->is_local || p->conf.debug) { + /* local procs get restarted */ + + log_error_write(srv, __FILE__, __LINE__, "sssd", + "establishing connection failed:", strerror(socket_error), + "port:", hctx->proc->port); + } + + hctx->proc->disabled_until = srv->cur_ts + 10; + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".died"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + return HANDLER_ERROR; + } + /* go on with preparing the request */ + hctx->state = FCGI_STATE_PREPARE_WRITE; + } + + + switch(hctx->state) { + case FCGI_STATE_CONNECT_DELAYED: + /* should never happen */ + break; + case FCGI_STATE_INIT: + /* do we have a running process for this host (max-procs) ? */ + + 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; + } + + ret = host->unixsocket->used ? AF_UNIX : AF_INET; + + if (-1 == (hctx->fd = socket(ret, 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; + } + + if (hctx->proc->is_local) { + hctx->pid = hctx->proc->pid; + } + + switch (fcgi_establish_connection(srv, hctx)) { + case CONNECTION_DELAYED: + /* connection is in progress, wait for an event and call getsockopt() below */ + + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT_DELAYED); + return HANDLER_WAIT_FOR_EVENT; + case CONNECTION_OVERLOADED: + /* cool down the backend, it is overloaded + * -> EAGAIN */ + + log_error_write(srv, __FILE__, __LINE__, "ssdsdb", + "backend is overloaded, we disable it for a 2 seconds and send the request to another backend instead:", + "reconnects:", hctx->reconnects, + "load:", host->load, + host->unixsocket); + + + hctx->proc->disabled_until = srv->cur_ts + 2; + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".overloaded"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + return HANDLER_ERROR; + case CONNECTION_DEAD: + /* we got a hard error from the backend like + * - ECONNREFUSED for tcp-ip sockets + * - ENOENT for unix-domain-sockets + * + * for check if the host is back in 10 seconds + * */ + + hctx->proc->disabled_until = srv->cur_ts + 10; + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".died"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + return HANDLER_ERROR; + case CONNECTION_OK: + /* everything is ok, go on */ + + fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE); + + break; + case CONNECTION_UNSET: + break; + } + + case FCGI_STATE_PREPARE_WRITE: + /* ok, we have the connection */ + + hctx->proc->load++; + hctx->proc->last_used = srv->cur_ts; + hctx->got_proc = 1; + + status_counter_inc(srv, CONST_STR_LEN("fastcgi.requests")); + status_counter_inc(srv, CONST_STR_LEN("fastcgi.active-requests")); + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".connected"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".load"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), hctx->proc->load); + + 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); + } + + /* fall through */ + fcgi_create_env(srv, hctx, hctx->request_id); + + fcgi_set_state(srv, hctx, FCGI_STATE_WRITE); + + /* fall through */ + case FCGI_STATE_WRITE: + ret = srv->network_backend_write(srv, con, hctx->fd, hctx->wb); + + chunkqueue_remove_finished_chunks(hctx->wb); + + if (ret < 0) { + switch(errno) { + case ENOTCONN: + /* the connection got dropped after accept() + * + * this is most of the time a PHP which dies + * after PHP_FCGI_MAX_REQUESTS + * + */ + if (hctx->wb->bytes_out == 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->wb->bytes_out, + "reconnect attempts:", hctx->reconnects); + + return HANDLER_ERROR; + case EAGAIN: + case EINTR: + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + return HANDLER_WAIT_FOR_EVENT; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "write failed:", strerror(errno), errno); + + return HANDLER_ERROR; + } + } + + if (hctx->wb->bytes_out == hctx->wb->bytes_in) { + /* we don't need the out event anymore */ + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + fcgi_set_state(srv, hctx, FCGI_STATE_READ); + } else { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + return HANDLER_WAIT_FOR_EVENT; + } + + 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; +} + + +/* might be called on fdevent after a connect() is delay too + * */ +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; + + /* we don't have a host yet, choose one + * -> this happens in the first round + * and when the host died and we have to select a new one */ + if (hctx->host == NULL) { + size_t k; + int ndx, used = -1; + + /* get best server */ + for (k = 0, ndx = -1; k < hctx->ext->used; k++) { + host = hctx->ext->hosts[k]; + + /* we should have at least one proc that can do something */ + if (host->active_procs == 0) continue; + + if (used == -1 || host->load < used) { + used = host->load; + + ndx = k; + } + } + + /* found a server */ + if (ndx == -1) { + /* all hosts are down */ + + fcgi_connection_close(srv, hctx); + + con->http_status = 500; + con->mode = DIRECT; + + return HANDLER_FINISHED; + } + + host = hctx->ext->hosts[ndx]; + + /* + * if check-local is disabled, use the uri.path handler + * + */ + + /* init handler-context */ + hctx->host = host; + + /* we put a connection on this host, move the other new connections to other hosts + * + * as soon as hctx->host is unassigned, decrease the load again */ + hctx->host->load++; + hctx->proc = NULL; + } else { + host = hctx->host; + } + + /* ok, create the request */ + switch(fcgi_write_request(srv, hctx)) { + case HANDLER_ERROR: + proc = hctx->proc; + host = hctx->host; + + if (hctx->state == FCGI_STATE_INIT || + hctx->state == FCGI_STATE_CONNECT_DELAYED) { + /* connect() or getsockopt() failed, + * restart the request-handling + */ + if (proc) { + if (proc->is_local) { + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sbdb", + "connect() to fastcgi failed, restarting the request-handling:", + host->host, + proc->port, + proc->socket); + } + + /* + * several hctx might reference the same proc + * + * Only one of them should mark the proc as dead all the other + * ones should just take a new one. + * + * If a new proc was started with the old struct this might lead + * the mark a perfect proc as dead otherwise + * + */ + if (proc->state == PROC_STATE_RUNNING && + hctx->pid == proc->pid) { + proc->state = PROC_STATE_DIED_WAIT_FOR_PID; + } + } else { + proc->state = PROC_STATE_DISABLED; + } + host->active_procs--; + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".disabled"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 1); + } + + fcgi_restart_dead_procs(srv, p, host); + + /* cleanup this request and let the request handler start this request again */ + if (hctx->reconnects < 5) { + fcgi_reconnect(srv, hctx); + joblist_append(srv, con); /* in case we come from the event-handler */ + + return HANDLER_WAIT_FOR_FD; + } else { + fcgi_connection_close(srv, hctx); + + buffer_reset(con->physical.path); + con->mode = DIRECT; + con->http_status = 500; + joblist_append(srv, con); /* in case we come from the event-handler */ + + return HANDLER_FINISHED; + } + } else { + fcgi_connection_close(srv, hctx); + + buffer_reset(con->physical.path); + con->mode = DIRECT; + con->http_status = 503; + joblist_append(srv, con); /* really ? */ + + 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: + log_error_write(srv, __FILE__, __LINE__, "s", "subrequest write-req default"); + return HANDLER_ERROR; + } +} + +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; + + 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_close(srv, hctx); + + con->mode = DIRECT; + con->file_started = 1; /* fcgi_extension won't touch the request afterwards */ + } else { + /* we are done */ + fcgi_connection_close(srv, hctx); + } + + joblist_append(srv, con); + return HANDLER_FINISHED; + case -1: + if (proc->pid && proc->state != PROC_STATE_DIED) { + int status; + + /* only fetch the zombie if it is not already done */ + + 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 (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", + "--- fastcgi spawning", + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tcurrent:", 1, "/", host->min_procs); + } + + 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->wb->bytes_out == 0 && + hctx->reconnects < 5) { + fcgi_reconnect(srv, hctx); + + log_error_write(srv, __FILE__, __LINE__, "ssdsd", + "response not received, request not sent, reconnecting.", + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + return HANDLER_WAIT_FOR_FD; + } + + log_error_write(srv, __FILE__, __LINE__, "sosdsd", + "response not received, request sent:", hctx->wb->bytes_out, + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + fcgi_connection_close(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_close(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); + } + + /* */ + + + joblist_append(srv, con); + return HANDLER_FINISHED; + } + } + + if (revents & FDEVENT_OUT) { + if (hctx->state == FCGI_STATE_CONNECT_DELAYED || + 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_DELAYED) { + /* 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); + joblist_append(srv, con); + } + } 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); + joblist_append(srv, con); + } + + return HANDLER_FINISHED; +} +#define PATCH(x) \ + p->conf.x = s->x; +static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(exts); + PATCH(debug); + PATCH(ext_mapping); + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.map-extensions"))) { + PATCH(ext_mapping); + } + } + } + + 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; + size_t k; + buffer *fn; + fcgi_extension *extension = NULL; + fcgi_extension_host *host = NULL; + + /* 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; + + fcgi_patch_connection(srv, con, p); + + /* fastcgi.map-extensions maps extensions to existing fastcgi.server entries + * + * fastcgi.map-extensions = ( ".php3" => ".php" ) + * + * fastcgi.server = ( ".php" => ... ) + * + * */ + + /* check if extension-mapping matches */ + for (k = 0; k < p->conf.ext_mapping->used; k++) { + data_string *ds = (data_string *)p->conf.ext_mapping->data[k]; + size_t ct_len; /* length of the config entry */ + + if (ds->key->used == 0) continue; + + ct_len = ds->key->used - 1; + + if (s_len < ct_len) continue; + + /* found a mapping */ + if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) { + /* check if we know the extension */ + + /* we can reuse k here */ + for (k = 0; k < p->conf.exts->used; k++) { + extension = p->conf.exts->exts[k]; + + if (buffer_is_equal(ds->value, extension->key)) { + break; + } + } + + if (k == p->conf.exts->used) { + /* found nothign */ + extension = NULL; + } + break; + } + } + + if (extension == NULL) { + /* check if extension matches */ + for (k = 0; k < p->conf.exts->used; k++) { + size_t ct_len; /* length of the config entry */ + + 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) { + 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; + } + } + + /* check if we have at least one server for this extension up and running */ + for (k = 0; k < extension->used; k++) { + host = extension->hosts[k]; + + /* we should have at least one proc that can do somthing */ + if (host->active_procs == 0) { + host = NULL; + + continue; + } + + /* we found one host that is alive */ + break; + } + + if (!host) { + /* sorry, we don't have a server alive for this ext */ + 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; + } + + /* + * 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; + char *pathinfo; + + hctx = handler_ctx_init(); + + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->proc = NULL; + hctx->ext = extension; + + + hctx->conf.exts = p->conf.exts; + hctx->conf.debug = p->conf.debug; + + con->plugin_ctx[p->id] = hctx; + + con->mode = p->id; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", + "handling it in mod_fastcgi"); + } + + /* the prefix is the SCRIPT_NAME, + * everthing from start to the next slash + * this is important for check-local = "disable" + * + * if prefix = /admin.fcgi + * + * /admin.fcgi/foo/bar + * + * SCRIPT_NAME = /admin.fcgi + * PATH_INFO = /foo/bar + * + * if prefix = /fcgi-bin/ + * + * /fcgi-bin/foo/bar + * + * SCRIPT_NAME = /fcgi-bin/foo + * PATH_INFO = /bar + * + */ + + /* the rewrite is only done for /prefix/? matches */ + if (extension->key->ptr[0] == '/' && + con->uri.path->used > extension->key->used && + NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) { + /* rewrite uri.path and pathinfo */ + + buffer_copy_string(con->request.pathinfo, pathinfo); + + con->uri.path->used -= con->request.pathinfo->used - 1; + con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + } + } + } else { + handler_ctx *hctx; + hctx = handler_ctx_init(); + + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->proc = NULL; + hctx->ext = extension; + + hctx->conf.exts = p->conf.exts; + hctx->conf.debug = p->conf.debug; + + con->plugin_ctx[p->id] = hctx; + + con->mode = p->id; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi"); + } + } + + 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_DELAYED: + 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; + + fcgi_connection_close(srv, con->plugin_ctx[p->id]); + + return HANDLER_GO_ON; +} + +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_indexfile.c b/src/mod_indexfile.c new file mode 100644 index 0000000..4a784c6 --- /dev/null +++ b/src/mod_indexfile.c @@ -0,0 +1,219 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "stat_cache.h" + +/* plugin config for all request/connections */ + +typedef struct { + array *indexfiles; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_indexfile_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_indexfile_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) continue; + + array_free(s->indexfiles); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_indexfile_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "index-file.names", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, 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->indexfiles = array_init(); + + cv[0].destination = s->indexfiles; + cv[1].destination = s->indexfiles; /* old name for [0] */ + + 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_indexfile_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(indexfiles); + + /* 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]; + s = p->config_storage[i]; + + /* 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.indexfiles"))) { + PATCH(indexfiles); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("index-file.names"))) { + PATCH(indexfiles); + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_indexfile_subrequest) { + plugin_data *p = p_d; + size_t k; + stat_cache_entry *sce = NULL; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (con->uri.path->ptr[con->uri.path->used - 2] != '/') return HANDLER_GO_ON; + + mod_indexfile_patch_connection(srv, con, p); + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling the request as Indexfile"); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI :", con->uri.path); + } + + /* indexfile */ + for (k = 0; k < p->conf.indexfiles->used; k++) { + data_string *ds = (data_string *)p->conf.indexfiles->data[k]; + + if (ds->value && ds->value->ptr[0] == '/') { + /* if the index-file starts with a prefix as use this file as + * index-generator */ + buffer_copy_string_buffer(p->tmp_buf, con->physical.doc_root); + } else { + buffer_copy_string_buffer(p->tmp_buf, con->physical.path); + } + buffer_append_string_buffer(p->tmp_buf, ds->value); + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) { + 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; + + log_error_write(srv, __FILE__, __LINE__, "ssbsb", + "file not found ... or so: ", strerror(errno), + con->uri.path, + "->", con->physical.path); + + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + continue; + } + + /* rewrite uri.path to the real path (/ -> /index.php) */ + buffer_append_string_buffer(con->uri.path, ds->value); + buffer_copy_string_buffer(con->physical.path, p->tmp_buf); + + /* fce is already set up a few lines above */ + + return HANDLER_GO_ON; + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_indexfile_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("indexfile"); + + p->init = mod_indexfile_init; + p->handle_subrequest_start = mod_indexfile_subrequest; + p->set_defaults = mod_indexfile_set_defaults; + p->cleanup = mod_indexfile_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_mysql_vhost.c b/src/mod_mysql_vhost.c new file mode 100644 index 0000000..bf13e09 --- /dev/null +++ b/src/mod_mysql_vhost.c @@ -0,0 +1,431 @@ +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <strings.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_MYSQL +#include <mysql.h> +#endif + +#include "plugin.h" +#include "log.h" + +#include "stat_cache.h" +#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 + */ + +#ifdef HAVE_MYSQL +typedef struct { + MYSQL *mysql; + + buffer *mydb; + buffer *myuser; + buffer *mypass; + buffer *mysock; + + buffer *hostname; + unsigned short port; + + 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]; + + if (!s) continue; + + mysql_close(s->mysql); + + 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 }, + { "mysql-vhost.hostname", NULL, T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER }, + { "mysql-vhost.port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, + { 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; + buffer *sel; + + + s = calloc(1, sizeof(plugin_config)); + s->mydb = buffer_init(); + s->myuser = buffer_init(); + s->mypass = buffer_init(); + s->mysock = buffer_init(); + s->hostname = buffer_init(); + s->port = 0; /* default port for mysql */ + sel = buffer_init(); + s->mysql = NULL; + + 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; + cv[5].destination = s->hostname; + cv[6].destination = &(s->port); + + 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); + } + + /* required: + * - username + * - database + * + * optional: + * - password, default: empty + * - socket, default: mysql default + * - hostname, if set overrides socket + * - port, default: 3306 + */ + + /* all have to be set */ + if (!(buffer_is_empty(s->myuser) || + buffer_is_empty(s->mydb))) { + + int fd; + + if (NULL == (s->mysql = mysql_init(NULL))) { + log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting..."); + + return HANDLER_ERROR; + } +#define FOO(x) (s->x->used ? s->x->ptr : NULL) + + if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass), + FOO(mydb), s->port, FOO(mysock), 0)) { + log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql)); + + return HANDLER_ERROR; + } +#undef FOO + /* 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); + } + } + } + + + + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(mysql_pre); + PATCH(mysql_post); +#ifdef HAVE_MYSQL + PATCH(mysql); +#endif + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } + } + + if (s->mysql) { + PATCH(mysql); + } + } + + return 0; +} +#undef PATCH + + +/* handle document root request */ +CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) { + plugin_data *p = p_d; + plugin_connection_data *c; + stat_cache_entry *sce; + + unsigned cols; + MYSQL_ROW row; + MYSQL_RES *result = NULL; + + /* no host specified? */ + if (!con->uri.authority->used) return HANDLER_GO_ON; + + mod_mysql_vhost_patch_connection(srv, con, p); + + if (!p->conf.mysql) return HANDLER_GO_ON; + + /* 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 (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf); + goto ERR500; + } + if (!S_ISDIR(sce->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; +} + +/* 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_request_done = mod_mysql_vhost_handle_connection_close; + + p->set_defaults = mod_mysql_vhost_set_defaults; + p->handle_docroot = mod_mysql_vhost_handle_docroot; + + return 0; +} +#else +/* we don't have mysql support, this plugin does nothing */ +int mod_mysql_vhost_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("mysql_vhost"); + + return 0; +} +#endif diff --git a/src/mod_proxy.c b/src/mod_proxy.c new file mode 100644 index 0000000..572de62 --- /dev/null +++ b/src/mod_proxy.c @@ -0,0 +1,1324 @@ +#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 "crc32.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 enum { + PROXY_BALANCE_UNSET, + PROXY_BALANCE_FAIR, + PROXY_BALANCE_HASH, + PROXY_BALANCE_RR +} proxy_balance_t; + +typedef struct { + array *extensions; + int debug; + + proxy_balance_t balance; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *parse_response; + buffer *balance_buf; + + 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; + + chunkqueue *wb; + + 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->wb = chunkqueue_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); + chunkqueue_free(hctx->wb); + + free(hctx); +} + +INIT_FUNC(mod_proxy_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->parse_response = buffer_init(); + p->balance_buf = buffer_init(); + + return p; +} + + +FREE_FUNC(mod_proxy_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + buffer_free(p->parse_response); + buffer_free(p->balance_buf); + + 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 */ + { "proxy.balance", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { 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); + cv[2].destination = p->balance_buf; + + buffer_reset(p->balance_buf); + + 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 (buffer_is_empty(p->balance_buf)) { + s->balance = PROXY_BALANCE_FAIR; + } else if (buffer_is_equal_string(p->balance_buf, CONST_STR_LEN("fair"))) { + s->balance = PROXY_BALANCE_FAIR; + } else if (buffer_is_equal_string(p->balance_buf, CONST_STR_LEN("round-robin"))) { + s->balance = PROXY_BALANCE_RR; + } else if (buffer_is_equal_string(p->balance_buf, CONST_STR_LEN("hash"))) { + s->balance = PROXY_BALANCE_HASH; + } else { + log_error_write(srv, __FILE__, __LINE__, "sb", + "proxy.balance has to be one of: fair, round-robin, hash, but not:", p->balance_buf); + 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(); + + df->port = 80; + + 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; + } + + /* 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_close(server *srv, handler_ctx *hctx) { + plugin_data *p; + connection *con; + + if (NULL == hctx) return; + + p = hctx->plugin_data; + con = hctx->remote_conn; + + 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--; + } + + handler_ctx_free(hctx); + con->plugin_ctx[p->id] = NULL; +} + +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 1; + } else { + + log_error_write(srv, __FILE__, __LINE__, "sdsd", + "connect failed:", proxy_fd, strerror(errno), errno); + + return -1; + } + } + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect succeeded: ", proxy_fd); + } + + return 0; +} + +void proxy_set_header(connection *con, const char *key, const char *value) { + 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(ds_dst->key, key); + buffer_copy_string(ds_dst->value, value); + array_insert_unique(con->request.headers, (data_unset *)ds_dst); +} + +void proxy_append_header(connection *con, const char *key, const char *value) { + 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(ds_dst->key, key); + buffer_append_string(ds_dst->value, value); + array_insert_unique(con->request.headers, (data_unset *)ds_dst); +} + + +static int proxy_create_env(server *srv, handler_ctx *hctx) { + size_t i; + + connection *con = hctx->remote_conn; + buffer *b; + + /* build header */ + + b = chunkqueue_get_append_buffer(hctx->wb); + + /* request line */ + buffer_copy_string(b, get_http_method_name(con->request.http_method)); + BUFFER_APPEND_STRING_CONST(b, " "); + + buffer_append_string_buffer(b, con->request.uri); + BUFFER_APPEND_STRING_CONST(b, " HTTP/1.0\r\n"); + + proxy_append_header(con, "X-Forwarded-For", (char *)inet_ntop_cache_get_ip(srv, &(con->dst_addr))); + /* http_host is NOT is just a pointer to a buffer + * which is NULL if it is not set */ + if (con->request.http_host && + !buffer_is_empty(con->request.http_host)) { + proxy_set_header(con, "X-Host", con->request.http_host->ptr); + } + proxy_set_header(con, "X-Forwarded-Proto", con->conf.is_ssl ? "https" : "http"); + + /* 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 (buffer_is_equal_string(ds->key, CONST_STR_LEN("Connection"))) continue; + + buffer_append_string_buffer(b, ds->key); + BUFFER_APPEND_STRING_CONST(b, ": "); + buffer_append_string_buffer(b, ds->value); + BUFFER_APPEND_STRING_CONST(b, "\r\n"); + } + } + + BUFFER_APPEND_STRING_CONST(b, "\r\n"); + + hctx->wb->bytes_in += b->used - 1; + /* body */ + + if (con->request.content_length) { + chunkqueue *req_cq = con->request_content_queue; + chunk *req_c; + off_t offset; + + /* something to send ? */ + for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) { + off_t weWant = req_cq->bytes_in - offset; + off_t weHave = 0; + + /* we announce toWrite octects + * now take all the request_content chunk that we need to fill this request + * */ + + switch (req_c->type) { + case FILE_CHUNK: + weHave = req_c->file.length - req_c->offset; + + if (weHave > weWant) weHave = weWant; + + chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave); + + req_c->offset += weHave; + req_cq->bytes_out += weHave; + + hctx->wb->bytes_in += weHave; + + break; + case MEM_CHUNK: + /* append to the buffer */ + weHave = req_c->mem->used - 1 - req_c->offset; + + if (weHave > weWant) weHave = weWant; + + b = chunkqueue_get_append_buffer(hctx->wb); + buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave); + b->used++; /* add virtual \0 */ + + req_c->offset += weHave; + req_cq->bytes_out += weHave; + + hctx->wb->bytes_in += weHave; + + break; + default: + break; + } + + offset += weHave; + } + + } + + 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; + int copy_header; + + 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++; + + copy_header = 1; + + 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)) { + copy_header = 0; + } + 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; + } + + if (copy_header) { + 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); + } + } + + 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 (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "proxy - have to read:", b); + } + + 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 handler_t proxy_write_request(server *srv, handler_ctx *hctx) { + data_proxy *host= hctx->host; + plugin_data *p = hctx->plugin_data; + connection *con = hctx->remote_conn; + + int ret; + + if (!host || + (!host->host->used || !host->port)) return -1; + + switch(hctx->state) { + case PROXY_STATE_INIT: + if (-1 == (hctx->fd = socket(AF_INET, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed: ", strerror(errno)); + return HANDLER_ERROR; + } + 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)); + + return HANDLER_ERROR; + } + + /* fall through */ + + case PROXY_STATE_CONNECT: + /* try to finish the connect() */ + if (hctx->state == PROXY_STATE_INIT) { + /* first round */ + switch (proxy_establish_connection(srv, hctx)) { + case 1: + proxy_set_state(srv, hctx, PROXY_STATE_CONNECT); + + /* connection is in progress, wait for an event and call getsockopt() below */ + + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + 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); + + /* we don't need it anymore */ + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + + /* 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->host->port); + + return HANDLER_ERROR; + } + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "s", "proxy - connect - delayed success"); + } + } + + 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); + + /* fall through */ + case PROXY_STATE_WRITE:; + ret = srv->network_backend_write(srv, con, hctx->fd, hctx->wb); + + chunkqueue_remove_finished_chunks(hctx->wb); + + if (-1 == ret) { + if (errno != EAGAIN && + errno != EINTR) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), errno); + + return HANDLER_ERROR; + } else { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + return HANDLER_WAIT_FOR_EVENT; + } + } + + if (hctx->wb->bytes_out == hctx->wb->bytes_in) { + proxy_set_state(srv, hctx, PROXY_STATE_READ); + + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + return HANDLER_WAIT_FOR_EVENT; + } + + return HANDLER_WAIT_FOR_EVENT; + case PROXY_STATE_READ: + /* waiting for a response */ + return HANDLER_WAIT_FOR_EVENT; + default: + log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state"); + return HANDLER_ERROR; + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_proxy_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(extensions); + PATCH(debug); + PATCH(balance); + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.balance"))) { + PATCH(balance); + } + } + } + + 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; + + if (NULL == hctx) return HANDLER_GO_ON; + + mod_proxy_patch_connection(srv, con, p); + + host = hctx->host; + + /* not my job */ + if (con->mode != p->id) return HANDLER_GO_ON; + + /* ok, create the request */ + switch(proxy_write_request(srv, hctx)) { + case HANDLER_ERROR: + log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:", + host->host, + host->port, + hctx->fd); + + /* disable this server */ + host->is_disabled = 1; + host->disable_ts = srv->cur_ts; + + proxy_connection_close(srv, hctx); + + /* reset the enviroment and restart the sub-request */ + 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; + case HANDLER_WAIT_FOR_EVENT: + return HANDLER_WAIT_FOR_EVENT; + case HANDLER_WAIT_FOR_FD: + return HANDLER_WAIT_FOR_FD; + default: + break; + } + + if (con->file_started == 1) { + return HANDLER_FINISHED; + } else { + return HANDLER_WAIT_FOR_EVENT; + } +} + +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; + + + if ((revents & FDEVENT_IN) && + hctx->state == PROXY_STATE_READ) { + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "proxy: fdevent-in", hctx->state); + } + + switch (proxy_demux_response(srv, hctx)) { + case 0: + break; + case 1: + hctx->host->usage--; + + /* we are done */ + proxy_connection_close(srv, hctx); + + joblist_append(srv, con); + 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); + } + + joblist_append(srv, con); + return HANDLER_FINISHED; + } + } + + if (revents & FDEVENT_OUT) { + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "proxy: fdevent-out", hctx->state); + } + + 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) { + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "proxy: fdevent-hup", hctx->state); + } + + if (hctx->state == PROXY_STATE_CONNECT) { + /* connect() -> EINPROGRESS -> HUP */ + + /** + * what is proxy is doing if it can't reach the next hop ? + * + */ + + proxy_connection_close(srv, hctx); + joblist_append(srv, con); + + con->http_status = 503; + con->mode = DIRECT; + + return HANDLER_FINISHED; + } + + con->file_finished = 1; + + proxy_connection_close(srv, hctx); + joblist_append(srv, con); + } else if (revents & FDEVENT_ERR) { + /* kill all connections to the proxy process */ + + log_error_write(srv, __FILE__, __LINE__, "sd", "proxy-FDEVENT_ERR, but no HUP", revents); + + joblist_append(srv, con); + proxy_connection_close(srv, hctx); + } + + 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; + unsigned long last_max = ULONG_MAX; + int max_usage = INT_MAX; + int ndx = -1; + size_t k; + 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; + + mod_proxy_patch_connection(srv, con, p); + + fn = con->uri.path; + + if (fn->used == 0) { + return HANDLER_ERROR; + } + + s_len = fn->used - 1; + + + path_info_offset = 0; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "s", "proxy - start"); + } + + /* 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; + } + } + + if (k == p->conf.extensions->used) { + return HANDLER_GO_ON; + } + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "s", "proxy - ext found"); + } + + switch(p->conf.balance) { + case PROXY_BALANCE_HASH: + /* hash balancing */ + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "proxy - used hash balancing, hosts:", extension->value->used); + } + + for (k = 0, ndx = -1, last_max = ULONG_MAX; k < extension->value->used; k++) { + data_proxy *host = (data_proxy *)extension->value->data[k]; + unsigned long cur_max; + + if (host->is_disabled) continue; + + cur_max = generate_crc32c(CONST_BUF_LEN(con->uri.path)) + + generate_crc32c(CONST_BUF_LEN(host->host)) + /* we can cache this */ + generate_crc32c(CONST_BUF_LEN(con->uri.authority)); + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sbbbd", + "proxy - election:", + con->uri.path, + host->host, + con->uri.authority, + cur_max); + } + + if ((last_max == ULONG_MAX) || /* first round */ + (cur_max > last_max)) { + last_max = cur_max; + + ndx = k; + } + } + + break; + case PROXY_BALANCE_FAIR: + /* fair balancing */ + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "s", + "proxy - used fair balancing"); + } + + for (k = 0, ndx = -1, max_usage = INT_MAX; k < extension->value->used; k++) { + data_proxy *host = (data_proxy *)extension->value->data[k]; + + if (host->is_disabled) continue; + + if (host->usage < max_usage) { + max_usage = host->usage; + + ndx = k; + } + } + + break; + case PROXY_BALANCE_RR: + /* round robin */ + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "s", + "proxy - used round-robin balancing"); + } + + /* just to be sure */ + assert(extension->value->used < INT_MAX); + + for (k = 0, ndx = -1, max_usage = INT_MAX; k < extension->value->used; k++) { + data_proxy *host = (data_proxy *)extension->value->data[k]; + + if (host->is_disabled) continue; + + /* first usable ndx */ + if (max_usage == INT_MAX) { + max_usage = k; + } + + /* get next ndx */ + if ((int)k > host->last_used_ndx) { + ndx = k; + host->last_used_ndx = k; + + break; + } + } + + /* didn't found a higher id, wrap to the start */ + if (ndx != -1 && max_usage != INT_MAX) { + ndx = max_usage; + } + + break; + default: + break; + } + + /* 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; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sbd", + "proxy - found a host", + host->host, host->port); + } + + 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; +} + +static handler_t mod_proxy_connection_close_callback(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + proxy_connection_close(srv, con->plugin_ctx[p->id]); + + return HANDLER_GO_ON; +} + +/** + * + * the trigger re-enables the disabled connections after the timeout is over + * + * */ + +TRIGGER_FUNC(mod_proxy_trigger) { + plugin_data *p = p_d; + + if (p->config_storage) { + size_t i, n, k; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if (!s) continue; + + /* get the extensions for all configs */ + + for (k = 0; k < s->extensions->used; k++) { + data_array *extension = (data_array *)s->extensions->data[k]; + + /* get all hosts */ + for (n = 0; n < extension->value->used; n++) { + data_proxy *host = (data_proxy *)extension->value->data[n]; + + if (!host->is_disabled || + srv->cur_ts - host->disable_ts < 5) continue; + + log_error_write(srv, __FILE__, __LINE__, "sbd", + "proxy - re-enabled:", + host->host, host->port); + + host->is_disabled = 0; + } + } + } + } + + return HANDLER_GO_ON; +} + + +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_close_callback; /* end of req-resp cycle */ + p->handle_connection_close = mod_proxy_connection_close_callback; /* end of client connection */ + p->handle_uri_clean = mod_proxy_check_extension; + p->handle_subrequest = mod_proxy_handle_subrequest; + p->handle_trigger = mod_proxy_trigger; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_redirect.c b/src/mod_redirect.c new file mode 100644 index 0000000..079e756 --- /dev/null +++ b/src/mod_redirect.c @@ -0,0 +1,277 @@ +#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; + data_config *context; /* to which apply me */ +} 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; + + 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]; + + pcre_keyvalue_buffer_free(s->redirect); + + free(s); + } + free(p->config_storage); + } + + + 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 = calloc(1, 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 = calloc(1, 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + p->conf.redirect = s->redirect; + + /* 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]; + s = p->config_storage[i]; + + /* 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; + p->conf.context = dc; + } + } + } + + 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_patch_connection(srv, con, p); + + buffer_copy_string_buffer(p->match_buf, con->request.uri); + + for (i = 0; i < p->conf.redirect->used; i++) { + pcre *match; + pcre_extra *extra; + const char *pattern; + size_t pattern_len; + int n; + pcre_keyvalue *kv = p->conf.redirect->kv[i]; +# define N 10 + int ovec[N * 3]; + + match = kv->key; + extra = kv->key_extra; + pattern = kv->value->ptr; + pattern_len = kv->value->used - 1; + + if ((n = pcre_exec(match, extra, 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] == '$' || 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); + + if (pattern[k] == '$') { + /* n is always > 0 */ + if (num < (size_t)n) { + buffer_append_string(p->location, list[num]); + } + } else { + config_append_cond_match_buffer(con, p->conf.context, p->location, 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; + con->file_finished = 1; + + 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 0000000..ff152a9 --- /dev/null +++ b/src/mod_rewrite.c @@ -0,0 +1,450 @@ +#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 once; +} rewrite_rule; + +typedef struct { + rewrite_rule **ptr; + + size_t used; + size_t size; +} rewrite_rule_buffer; + +typedef struct { + rewrite_rule_buffer *rewrite; + data_config *context; /* to which apply me */ +} plugin_config; + +typedef struct { + enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state; + int loops; +} 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; + hctx->loops = 0; + + 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 once) { +#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]->once = once; + + kvb->used++; + + return 0; +#else + UNUSED(kvb); + UNUSED(value); + UNUSED(once); + 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; +} + +static int parse_config_entry(server *srv, plugin_config *s, array *ca, const char *option, int once) { + data_unset *du; + + if (NULL != (du = array_get_element(ca, option))) { + data_array *da = (data_array *)du; + size_t j; + + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", option, "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: ", + option, + "[", 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, + once)) { +#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 0; +} + +SETDEFAULTS_FUNC(mod_rewrite_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "url.rewrite-repeat", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "url.rewrite-once", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + + /* old names, still supported + * + * url.rewrite remapped to url.rewrite-once + * url.rewrite-final is url.rewrite-once + * + */ + { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + /* 0 */ + 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 = calloc(1, sizeof(plugin_config)); + s->rewrite = rewrite_rule_buffer_init(); + + cv[0].destination = s->rewrite; + cv[1].destination = s->rewrite; + cv[2].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; + } + + parse_config_entry(srv, s, ca, "url.rewrite-once", 1); + parse_config_entry(srv, s, ca, "url.rewrite-final", 1); + parse_config_entry(srv, s, ca, "url.rewrite", 1); + parse_config_entry(srv, s, ca, "url.rewrite-repeat", 0); + } + + return HANDLER_GO_ON; +} +#ifdef HAVE_PCRE_H +static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + p->conf.rewrite = s->rewrite; + + /* 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]; + s = p->config_storage[i]; + + if (COMP_HTTP_URL == dc->comp) 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; + p->conf.context = dc; + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) { + p->conf.rewrite = s->rewrite; + p->conf.context = dc; + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) { + p->conf.rewrite = s->rewrite; + p->conf.context = dc; + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) { + p->conf.rewrite = s->rewrite; + p->conf.context = dc; + } + } + } + + 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->loops++ > 100) { + log_error_write(srv, __FILE__, __LINE__, "s", + "ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request, perhaps you want to use url.rewrite-once instead of url.rewrite-repeat"); + + return HANDLER_ERROR; + } + + if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON; + } + + mod_rewrite_patch_connection(srv, con, p); + + if (!p->conf.rewrite) return HANDLER_GO_ON; + + 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] == '$' || 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); + + if (pattern[k] == '$') { + /* n is always > 0 */ + if (num < (size_t)n) { + buffer_append_string(con->request.uri, list[num]); + } + } else { + config_append_cond_match_buffer(con, p->conf.context, con->request.uri, 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->once) 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; + /* it has to stay _raw as we are matching on uri + querystring + */ + + 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 0000000..c7b897a --- /dev/null +++ b/src/mod_rrdtool.c @@ -0,0 +1,449 @@ +#define _GNU_SOURCE +#include <sys/types.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.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]); + + close(STDERR_FILENO); + + if (srv->errorlog_mode == ERRORLOG_FILE) { + dup2(srv->errorlog_fd, STDERR_FILENO); + close(srv->errorlog_fd); + } + + /* 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++) { + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + 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); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#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 = calloc(1, 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_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; + + mod_rrd_patch_connection(srv, con, p); + + *(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_scgi.c b/src/mod_scgi.c new file mode 100644 index 0000000..2c1df2f --- /dev/null +++ b/src/mod_scgi.c @@ -0,0 +1,3089 @@ +#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 <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 + +enum {EOL_UNSET, EOL_N, EOL_RN}; + +/* + * + * TODO: + * + * - add timeout for a connect to a non-scgi process + * (use state_timestamp + state) + * + */ + +typedef struct scgi_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 scgi_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; +} scgi_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 + */ + scgi_proc *first; + scgi_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 scgi 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 scgi + * 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; + + /* + * 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 scgi-files we should not stat() for them + * and say '404 not found'. + */ + unsigned short check_local; + + /* + * append PATH_INFO to SCRIPT_FILENAME + * + * php needs this if cgi.fix_pathinfo is provied + * + */ + + 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 */ +} scgi_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 scgi 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 */ + + scgi_extension_host **hosts; + + size_t used; + size_t size; +} scgi_extension; + +typedef struct { + scgi_extension **exts; + + size_t used; + size_t size; +} scgi_exts; + + +typedef struct { + scgi_exts *exts; + + int debug; +} plugin_config; + +typedef struct { + char **ptr; + + size_t size; + size_t used; +} char_array; + +/* generic plugin data, shared between all connections */ +typedef struct { + PLUGIN_DATA; + + buffer *scgi_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 +} scgi_connection_state_t; + +typedef struct { + buffer *response; + size_t response_len; + int response_type; + int response_padding; + + scgi_proc *proc; + scgi_extension_host *host; + + scgi_connection_state_t state; + time_t state_timestamp; + + int reconnects; /* number of reconnect attempts */ + + read_buffer *rb; + chunkqueue *wb; + + buffer *response_header; + + int delayed; /* flag to mark that the connect() is delayed */ + + size_t request_id; + int fd; /* fd to the scgi process */ + int fde_ndx; /* index into the fd-event buffer */ + + pid_t pid; + int got_proc; + + plugin_config conf; + + connection *remote_conn; /* dumb pointer */ + plugin_data *plugin_data; /* dumb pointer */ +} handler_ctx; + + +/* ok, we need a prototype */ +static handler_t scgi_handle_fdevent(void *s, void *ctx, int revents); + +int scgi_proclist_sort_down(server *srv, scgi_extension_host *host, scgi_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->request_id = 0; + hctx->state = FCGI_STATE_INIT; + hctx->proc = NULL; + + hctx->response_len = 0; + hctx->response_type = 0; + hctx->response_padding = 0; + hctx->fd = -1; + + hctx->reconnects = 0; + + hctx->wb = chunkqueue_init(); + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + buffer_free(hctx->response); + buffer_free(hctx->response_header); + + chunkqueue_free(hctx->wb); + + if (hctx->rb) { + if (hctx->rb->ptr) free(hctx->rb->ptr); + free(hctx->rb); + } + + free(hctx); +} + +scgi_proc *scgi_process_init() { + scgi_proc *f; + + f = calloc(1, sizeof(*f)); + f->socket = buffer_init(); + + f->prev = NULL; + f->next = NULL; + + return f; +} + +void scgi_process_free(scgi_proc *f) { + if (!f) return; + + scgi_process_free(f->next); + + buffer_free(f->socket); + + free(f); +} + +scgi_extension_host *scgi_host_init() { + scgi_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 scgi_host_free(scgi_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); + + scgi_process_free(h->first); + scgi_process_free(h->unused_procs); + + free(h); + +} + +scgi_exts *scgi_extensions_init() { + scgi_exts *f; + + f = calloc(1, sizeof(*f)); + + return f; +} + +void scgi_extensions_free(scgi_exts *f) { + size_t i; + + if (!f) return; + + for (i = 0; i < f->used; i++) { + scgi_extension *fe; + size_t j; + + fe = f->exts[i]; + + for (j = 0; j < fe->used; j++) { + scgi_extension_host *h; + + h = fe->hosts[j]; + + scgi_host_free(h); + } + + buffer_free(fe->key); + free(fe->hosts); + + free(fe); + } + + free(f->exts); + + free(f); +} + +int scgi_extension_insert(scgi_exts *ext, buffer *key, scgi_extension_host *fh) { + scgi_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_scgi_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->scgi_env = buffer_init(); + + p->path = buffer_init(); + p->parse_response = buffer_init(); + + return p; +} + + +FREE_FUNC(mod_scgi_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + buffer_free(p->scgi_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]; + scgi_exts *exts; + + if (!s) continue; + + exts = s->exts; + + for (j = 0; j < exts->used; j++) { + scgi_extension *ex; + + ex = exts->exts[j]; + + for (n = 0; n < ex->used; n++) { + scgi_proc *proc; + scgi_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); + } + } + } + } + + scgi_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 scgi_spawn_connection(server *srv, + plugin_data *p, + scgi_extension_host *host, + scgi_proc *proc) { + int scgi_fd; + int socket_type, status; + struct timeval tv = { 0, 100 * 1000 }; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un scgi_addr_un; +#endif + struct sockaddr_in scgi_addr_in; + struct sockaddr *scgi_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(&scgi_addr, 0, sizeof(scgi_addr)); + +#ifdef HAVE_SYS_UN_H + scgi_addr_un.sun_family = AF_UNIX; + strcpy(scgi_addr_un.sun_path, proc->socket->ptr); + +#ifdef SUN_LEN + servlen = SUN_LEN(&scgi_addr_un); +#else + /* stevens says: */ + servlen = proc->socket->used - 1 + sizeof(scgi_addr_un.sun_family); +#endif + socket_type = AF_UNIX; + scgi_addr = (struct sockaddr *) &scgi_addr_un; +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: Unix Domain sockets are not supported."); + return -1; +#endif + } else { + scgi_addr_in.sin_family = AF_INET; + + if (buffer_is_empty(host->host)) { + scgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + struct hostent *he; + + /* set a usefull default */ + scgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + + + if (NULL == (he = gethostbyname(host->host->ptr))) { + log_error_write(srv, __FILE__, __LINE__, + "sdb", "gethostbyname failed: ", + 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(&(scgi_addr_in.sin_addr.s_addr), he->h_addr_list[0], he->h_length); + + } + scgi_addr_in.sin_port = htons(proc->port); + servlen = sizeof(scgi_addr_in); + + socket_type = AF_INET; + scgi_addr = (struct sockaddr *) &scgi_addr_in; + } + + if (-1 == (scgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "failed:", strerror(errno)); + return -1; + } + + if (-1 == connect(scgi_fd, scgi_addr, servlen)) { + /* server is not up, spawn in */ + pid_t child; + int val; + + if (!buffer_is_empty(proc->socket)) { + unlink(proc->socket->ptr); + } + + close(scgi_fd); + + /* reopen socket */ + if (-1 == (scgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "socket failed:", strerror(errno)); + return -1; + } + + val = 1; + if (setsockopt(scgi_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(scgi_fd, scgi_addr, servlen)) { + log_error_write(srv, __FILE__, __LINE__, "sbds", + "bind failed for:", + proc->socket, + proc->port, + strerror(errno)); + return -1; + } + + if (-1 == listen(scgi_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; + int fd = 0; + char_array env; + + + /* create environment */ + env.ptr = NULL; + env.size = 0; + env.used = 0; + + /* we don't need the client socket */ + for (fd = 3; fd < 256; fd++) { + if (fd != 2 && fd != scgi_fd) close(fd); + } + + /* 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 SCGI 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(scgi_fd); + + return 0; +} + + +SETDEFAULTS_FUNC(mod_scgi_set_defaults) { + plugin_data *p = p_d; + data_unset *du; + size_t i = 0; + + config_values_t cv[] = { + { "scgi.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "scgi.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 = scgi_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, "scgi.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: ", "scgi.server", "array of strings"); + + return HANDLER_ERROR; + } + + + /* + * scgi.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: ", "scgi.server", + "[", da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + /* + * da_ext->key == name of the extension + */ + + /* + * scgi.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]; + + scgi_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 */ + { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + + { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ + { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ + { "min-procs-not-working", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 this is broken for now */ + { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ + { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */ + { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ + { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ + + { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ + { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ + + + { 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:", + "scgi.server", + "[", da_host->key, "](string)"); + + return HANDLER_ERROR; + } + + df = scgi_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->disable_time = 60; + + fcv[0].destination = df->host; + fcv[1].destination = df->docroot; + fcv[2].destination = df->unixsocket; + fcv[3].destination = df->bin_path; + + fcv[4].destination = &(df->check_local); + fcv[5].destination = &(df->port); + fcv[6].destination = &(df->min_procs); + fcv[7].destination = &(df->max_procs); + fcv[8].destination = &(df->max_load_per_proc); + fcv[9].destination = &(df->idle_timeout); + fcv[10].destination = &(df->disable_time); + + fcv[11].destination = df->bin_env; + fcv[12].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", + "--- scgi 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++) { + scgi_proc *proc; + + proc = scgi_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", + "--- scgi spawning", + "\n\tport:", df->port, + "\n\tsocket", df->unixsocket, + "\n\tcurrent:", pno, "/", df->min_procs); + } + + if (scgi_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 { + scgi_proc *fp; + + fp = scgi_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 extension already exists, take it */ + scgi_extension_insert(s->exts, da_ext->key, df); + } + } + } + } + + return HANDLER_GO_ON; +} + +static int scgi_set_state(server *srv, handler_ctx *hctx, scgi_connection_state_t state) { + hctx->state = state; + hctx->state_timestamp = srv->cur_ts; + + return 0; +} + + +void scgi_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->host && hctx->proc) { + hctx->host->load--; + + if (hctx->got_proc) { + /* 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); + } + } + + scgi_proclist_sort_down(srv, hctx->host, hctx->proc); + } + + + handler_ctx_free(hctx); + con->plugin_ctx[p->id] = NULL; +} + +static int scgi_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--; + + scgi_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--; + scgi_proclist_sort_down(srv, hctx->host, hctx->proc); + + return 0; +} + + +static handler_t scgi_connection_reset(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + scgi_connection_cleanup(srv, con->plugin_ctx[p->id]); + + return HANDLER_GO_ON; +} + + +static int scgi_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 + 2; + + buffer_prepare_append(env, len); + + /* include the NUL */ + memcpy(env->ptr + env->used, key, key_len + 1); + env->used += key_len + 1; + memcpy(env->ptr + env->used, val, val_len + 1); + env->used += val_len + 1; + + return 0; +} + + +/** + * + * returns + * -1 error + * 0 connected + * 1 not connected yet + */ + +static int scgi_establish_connection(server *srv, handler_ctx *hctx) { + struct sockaddr *scgi_addr; + struct sockaddr_in scgi_addr_in; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un scgi_addr_un; +#endif + socklen_t servlen; + + scgi_extension_host *host = hctx->host; + scgi_proc *proc = hctx->proc; + int scgi_fd = hctx->fd; + + memset(&scgi_addr, 0, sizeof(scgi_addr)); + + if (!buffer_is_empty(proc->socket)) { +#ifdef HAVE_SYS_UN_H + /* use the unix domain socket */ + scgi_addr_un.sun_family = AF_UNIX; + strcpy(scgi_addr_un.sun_path, proc->socket->ptr); +#ifdef SUN_LEN + servlen = SUN_LEN(&scgi_addr_un); +#else + /* stevens says: */ + servlen = proc->socket->used - 1 + sizeof(scgi_addr_un.sun_family); +#endif + scgi_addr = (struct sockaddr *) &scgi_addr_un; +#else + return -1; +#endif + } else { + scgi_addr_in.sin_family = AF_INET; + if (0 == inet_aton(host->host->ptr, &(scgi_addr_in.sin_addr))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "converting IP-adress failed for", host->host, + "\nBe sure to specify an IP address here"); + + return -1; + } + scgi_addr_in.sin_port = htons(proc->port); + servlen = sizeof(scgi_addr_in); + + scgi_addr = (struct sockaddr *) &scgi_addr_in; + } + + if (-1 == connect(scgi_fd, scgi_addr, servlen)) { + if (errno == EINPROGRESS || + errno == EALREADY || + errno == EINTR) { + if (hctx->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect delayed, will continue later:", scgi_fd); + } + + return 1; + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsddb", + "connect failed:", scgi_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: ", scgi_fd); + } + + + + return 0; +} + +static int scgi_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'; + + scgi_env_add(p->scgi_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'; + + scgi_env_add(p->scgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)); + } + } + + return 0; +} + + +static int scgi_create_env(server *srv, handler_ctx *hctx) { + char buf[32]; + const char *s; +#ifdef HAVE_IPV6 + char b2[INET6_ADDRSTRLEN + 1]; +#endif + buffer *b; + + plugin_data *p = hctx->plugin_data; + scgi_extension_host *host= hctx->host; + + connection *con = hctx->remote_conn; + server_socket *srv_sock = con->srv_socket; + + sock_addr our_addr; + socklen_t our_addr_len; + + buffer_prepare_copy(p->scgi_env, 1024); + + /* CGI-SPEC 6.1.2, FastCGI spec 6.3 and SCGI spec */ + + /* request.content_length < SSIZE_MAX, see request.c */ + ltostr(buf, con->request.content_length); + scgi_env_add(p->scgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)); + scgi_env_add(p->scgi_env, CONST_STR_LEN("SCGI"), CONST_STR_LEN("1")); + + + scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION)); + + if (con->server_name->used) { + scgi_env_add(p->scgi_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 + scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s)); + } + + scgi_env_add(p->scgi_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 + ); + + scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf)); + + /* get the server-side of the connection to the client */ + our_addr_len = sizeof(our_addr); + + if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) { + s = inet_ntop_cache_get_ip(srv, &(srv_sock->addr)); + } else { + s = inet_ntop_cache_get_ip(srv, &(our_addr)); + } + scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)); + + ltostr(buf, +#ifdef HAVE_IPV6 + ntohs(con->dst_addr.plain.sa_family ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port) +#else + ntohs(con->dst_addr.ipv4.sin_port) +#endif + ); + + scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf)); + + s = inet_ntop_cache_get_ip(srv, &(con->dst_addr)); + scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s)); + + if (!buffer_is_empty(con->authed_user)) { + scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_USER"), + CONST_BUF_LEN(con->authed_user)); + } + + + /* + * 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) + */ + + scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); + + if (!buffer_is_empty(con->request.pathinfo)) { + scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); + + /* PATH_TRANSLATED is only defined if PATH_INFO is set */ + + if (!buffer_is_empty(host->docroot)) { + buffer_copy_string_buffer(p->path, host->docroot); + } else { + buffer_copy_string_buffer(p->path, con->physical.doc_root); + } + buffer_append_string_buffer(p->path, con->request.pathinfo); + scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path)); + } else { + scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_INFO"), CONST_STR_LEN("")); + } + + /* + * 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); + + scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path)); + scgi_env_add(p->scgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot)); + } else { + buffer_copy_string_buffer(p->path, con->physical.path); + + scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path)); + scgi_env_add(p->scgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); + } + scgi_env_add(p->scgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); + if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) { + scgi_env_add(p->scgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri)); + } + if (!buffer_is_empty(con->uri.query)) { + scgi_env_add(p->scgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)); + } else { + scgi_env_add(p->scgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN("")); + } + + s = get_http_method_name(con->request.http_method); + scgi_env_add(p->scgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)); + scgi_env_add(p->scgi_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); + scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)); + +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + scgi_env_add(p->scgi_env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on")); + } +#endif + + scgi_env_add_request_headers(srv, con, p); + + b = chunkqueue_get_append_buffer(hctx->wb); + + buffer_append_long(b, p->scgi_env->used); + buffer_append_string_len(b, CONST_STR_LEN(":")); + buffer_append_string_len(b, (const char *)p->scgi_env->ptr, p->scgi_env->used); + buffer_append_string_len(b, CONST_STR_LEN(",")); + + hctx->wb->bytes_in += b->used - 1; + + if (con->request.content_length) { + chunkqueue *req_cq = con->request_content_queue; + chunk *req_c; + off_t offset; + + /* something to send ? */ + for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) { + off_t weWant = req_cq->bytes_in - offset; + off_t weHave = 0; + + /* we announce toWrite octects + * now take all the request_content chunk that we need to fill this request + * */ + + switch (req_c->type) { + case FILE_CHUNK: + weHave = req_c->file.length - req_c->offset; + + if (weHave > weWant) weHave = weWant; + + chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave); + + req_c->offset += weHave; + req_cq->bytes_out += weHave; + + hctx->wb->bytes_in += weHave; + + break; + case MEM_CHUNK: + /* append to the buffer */ + weHave = req_c->mem->used - 1 - req_c->offset; + + if (weHave > weWant) weHave = weWant; + + b = chunkqueue_get_append_buffer(hctx->wb); + buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave); + b->used++; /* add virtual \0 */ + + req_c->offset += weHave; + req_cq->bytes_out += weHave; + + hctx->wb->bytes_in += weHave; + + break; + default: + break; + } + + offset += weHave; + } + } + +#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 scgi_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 scgi_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 0; + } + /* error */ + log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd); + return -1; + } + + 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 1; + } + + 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 */ + scgi_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 0; +} + + +int scgi_proclist_sort_up(server *srv, scgi_extension_host *host, scgi_proc *proc) { + scgi_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 scgi_proclist_sort_down(server *srv, scgi_extension_host *host, scgi_proc *proc) { + scgi_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 scgi 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 int scgi_restart_dead_procs(server *srv, plugin_data *p, scgi_extension_host *host) { + scgi_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 > host->disable_time)) { + 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)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdsd", + "child exited, pid:", proc->pid, + "status:", WEXITSTATUS(status)); +#endif + } 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 (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", + "--- scgi spawning", + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tcurrent:", 1, "/", host->min_procs); + } + + if (scgi_spawn_connection(srv, p, host, proc)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: spawning fcgi failed."); + return HANDLER_ERROR; + } + + scgi_proclist_sort_down(srv, host, proc); + } + } + } + + return 0; +} + + +static handler_t scgi_write_request(server *srv, handler_ctx *hctx) { + plugin_data *p = hctx->plugin_data; + scgi_extension_host *host= hctx->host; + connection *con = hctx->remote_conn; + + int ret; + + /* sanity check */ + if (!host || + ((!host->host->used || !host->port) && !host->unixsocket->used)) { + log_error_write(srv, __FILE__, __LINE__, "sxddd", + "write-req: error", + host, + host->host->used, + host->port, + host->unixsocket->used); + return HANDLER_ERROR; + } + + + switch(hctx->state) { + case FCGI_STATE_INIT: + ret = host->unixsocket->used ? AF_UNIX : AF_INET; + + if (-1 == (hctx->fd = socket(ret, 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, scgi_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; + } + + if (hctx->proc->is_local) { + hctx->pid = hctx->proc->pid; + } + + switch (scgi_establish_connection(srv, hctx)) { + case 1: + scgi_set_state(srv, hctx, FCGI_STATE_CONNECT); + + /* connection is in progress, wait for an event and call getsockopt() below */ + + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + 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) { + if (!hctx->proc->is_local || p->conf.debug) { + /* local procs get restarted */ + + 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; + hctx->got_proc = 1; + + 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 */ + scgi_proclist_sort_up(srv, hctx->host, hctx->proc); + + scgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE); + /* fall through */ + case FCGI_STATE_PREPARE_WRITE: + scgi_create_env(srv, hctx); + + scgi_set_state(srv, hctx, FCGI_STATE_WRITE); + + /* fall through */ + case FCGI_STATE_WRITE: + ret = srv->network_backend_write(srv, con, hctx->fd, hctx->wb); + + chunkqueue_remove_finished_chunks(hctx->wb); + + if (-1 == ret) { + 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->wb->bytes_out == 0 && + hctx->reconnects < 5) { + usleep(10000); /* take away the load of the webserver + * to let the php a chance to restart + */ + + scgi_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->wb->bytes_out, + "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 { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + return HANDLER_WAIT_FOR_EVENT; + } + } + + if (hctx->wb->bytes_out == hctx->wb->bytes_in) { + /* we don't need the out event anymore */ + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + scgi_set_state(srv, hctx, FCGI_STATE_READ); + } else { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + return HANDLER_WAIT_FOR_EVENT; + } + + 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; +} + +SUBREQUEST_FUNC(mod_scgi_handle_subrequest) { + plugin_data *p = p_d; + + handler_ctx *hctx = con->plugin_ctx[p->id]; + scgi_proc *proc; + scgi_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(scgi_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) { + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sbdb", "connect() to scgi failed, restarting the request-handling:", + host->host, + proc->port, + proc->socket); + } + + /* + * several hctx might reference the same proc + * + * Only one of them should mark the proc as dead all the other + * ones should just take a new one. + * + * If a new proc was started with the old struct this might lead + * the mark a perfect proc as dead otherwise + * + */ + if (proc->state == PROC_STATE_RUNNING && + hctx->pid == proc->pid) { + proc->state = PROC_STATE_DIED_WAIT_FOR_PID; + } + } + scgi_restart_dead_procs(srv, p, host); + + scgi_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 { + scgi_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: + log_error_write(srv, __FILE__, __LINE__, "s", "subrequest write-req default"); + return HANDLER_ERROR; + } +} + +static handler_t scgi_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: scgi:", + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + + + scgi_connection_cleanup(srv, hctx); + + return HANDLER_FINISHED; +} + + +static handler_t scgi_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; + + scgi_proc *proc = hctx->proc; + scgi_extension_host *host= hctx->host; + + if ((revents & FDEVENT_IN) && + hctx->state == FCGI_STATE_READ) { + switch (scgi_demux_response(srv, hctx)) { + case 0: + break; + case 1: + /* we are done */ + scgi_connection_cleanup(srv, hctx); + + joblist_append(srv, con); + return HANDLER_FINISHED; + case -1: + if (proc->pid && proc->state != PROC_STATE_DIED) { + int status; + + /* only fetch the zombie if it is not already done */ + + 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 (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", + "--- scgi spawning", + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tcurrent:", 1, "/", host->min_procs); + } + + if (scgi_spawn_connection(srv, p, host, proc)) { + /* child died */ + proc->state = PROC_STATE_DIED; + } else { + scgi_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->wb->bytes_out == 0 && + hctx->reconnects < 5) { + scgi_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->wb->bytes_out, + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + scgi_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 */ + scgi_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); + } + + /* */ + + + joblist_append(srv, con); + 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_scgi_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_scgi_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 scgi connection for", + con->uri.path, + "(no scgi process on host: ", + host->host, + ", port: ", + host->port, + " ?)", + hctx->state); + + connection_set_state(srv, con, CON_STATE_ERROR); + scgi_connection_close(srv, hctx); + joblist_append(srv, con); + } + } 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 scgi process */ + + + connection_set_state(srv, con, CON_STATE_ERROR); + scgi_connection_close(srv, hctx); + joblist_append(srv, con); + } + + return HANDLER_FINISHED; +} +#define PATCH(x) \ + p->conf.x = s->x; +static int scgi_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(exts); + PATCH(debug); + + /* 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]; + s = p->config_storage[i]; + + /* 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("scgi.server"))) { + PATCH(exts); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("scgi.debug"))) { + PATCH(debug); + } + } + } + + return 0; +} +#undef PATCH + + +static handler_t scgi_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; + buffer *fn; + scgi_extension *extension = NULL; + + /* 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 (buffer_is_empty(fn)) return HANDLER_GO_ON; + + s_len = fn->used - 1; + + scgi_patch_connection(srv, con, p); + + /* 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 "/scgi_pattern" */ + if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) { + 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++) { + scgi_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) { + scgi_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; + char *pathinfo; + + hctx = handler_ctx_init(); + + 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; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_scgi"); + } + + /* the prefix is the SCRIPT_NAME, + * everthing from start to the next slash + * this is important for check-local = "disable" + * + * if prefix = /admin.fcgi + * + * /admin.fcgi/foo/bar + * + * SCRIPT_NAME = /admin.fcgi + * PATH_INFO = /foo/bar + * + * if prefix = /fcgi-bin/ + * + * /fcgi-bin/foo/bar + * + * SCRIPT_NAME = /fcgi-bin/foo + * PATH_INFO = /bar + * + */ + + /* the rewrite is only done for /prefix/? matches */ + if (extension->key->ptr[0] == '/' && + con->uri.path->used > extension->key->used && + NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) { + /* rewrite uri.path and pathinfo */ + + buffer_copy_string(con->request.pathinfo, pathinfo); + + con->uri.path->used -= con->request.pathinfo->used - 1; + con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + } + } + return HANDLER_GO_ON; + } else { + handler_ctx *hctx; + hctx = handler_ctx_init(); + + 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; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi"); + } + + return HANDLER_GO_ON; + } + } 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 scgi_check_extension_1(server *srv, connection *con, void *p_d) { + return scgi_check_extension(srv, con, p_d, 1); +} + +/* start request handler */ +static handler_t scgi_check_extension_2(server *srv, connection *con, void *p_d) { + return scgi_check_extension(srv, con, p_d, 0); +} + +JOBLIST_FUNC(mod_scgi_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 scgi_connection_close_callback(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + return scgi_connection_close(srv, con->plugin_ctx[p->id]); +} + +TRIGGER_FUNC(mod_scgi_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; + scgi_exts *exts; + + conf = p->config_storage[i]; + + exts = conf->exts; + + for (j = 0; j < exts->used; j++) { + scgi_extension *ex; + + ex = exts->exts[j]; + + for (n = 0; n < ex->used; n++) { + + scgi_proc *proc; + unsigned long sum_load = 0; + scgi_extension_host *host; + + host = ex->hosts[n]; + + scgi_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 */ + scgi_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 = scgi_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 (scgi_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_scgi_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("scgi"); + + p->init = mod_scgi_init; + p->cleanup = mod_scgi_free; + p->set_defaults = mod_scgi_set_defaults; + p->connection_reset = scgi_connection_reset; + p->handle_connection_close = scgi_connection_close_callback; + p->handle_uri_clean = scgi_check_extension_1; + p->handle_subrequest_start = scgi_check_extension_2; + p->handle_subrequest = mod_scgi_handle_subrequest; + p->handle_joblist = mod_scgi_handle_joblist; + p->handle_trigger = mod_scgi_handle_trigger; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_secure_download.c b/src/mod_secure_download.c new file mode 100644 index 0000000..1ea5a50 --- /dev/null +++ b/src/mod_secure_download.c @@ -0,0 +1,305 @@ +#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.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; + + unsigned short 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 = calloc(1, 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->secret = buffer_init(); + s->doc_root = buffer_init(); + s->uri_prefix = buffer_init(); + s->timeout = 60; + + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(secret); + PATCH(doc_root); + PATCH(uri_prefix); + PATCH(timeout); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#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_patch_connection(srv, con, p); + + if (buffer_is_empty(p->conf.uri_prefix)) return HANDLER_GO_ON; + + 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.doc_root, p->conf.doc_root); + buffer_copy_string(con->physical.rel_path, rel_uri); + buffer_copy_string_buffer(con->physical.path, con->physical.doc_root); + buffer_append_string_buffer(con->physical.path, con->physical.rel_path); + + return HANDLER_GO_ON; +} + +/* 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_physical = 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 0000000..9501554 --- /dev/null +++ b/src/mod_setenv.c @@ -0,0 +1,247 @@ +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "response.h" + +/* plugin config for all request/connections */ + +typedef struct { + int handled; /* make sure that we only apply the headers once */ +} handler_ctx; + +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; + +static handler_ctx * handler_ctx_init() { + handler_ctx * hctx; + + hctx = calloc(1, sizeof(*hctx)); + + hctx->handled = 0; + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + free(hctx); +} + + +/* 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 = calloc(1, 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->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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(request_header); + PATCH(response_header); + PATCH(environment); + + /* 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]; + s = p->config_storage[i]; + + /* 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.add-environment"))) { + PATCH(environment); + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_setenv_uri_handler) { + plugin_data *p = p_d; + size_t k; + handler_ctx *hctx; + + if (con->plugin_ctx[p->id]) { + hctx = con->plugin_ctx[p->id]; + } else { + hctx = handler_ctx_init(); + + con->plugin_ctx[p->id] = hctx; + } + + if (hctx->handled) { + return HANDLER_GO_ON; + } + + hctx->handled = 1; + + mod_setenv_patch_connection(srv, con, p); + + 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; +} + +REQUESTDONE_FUNC(mod_setenv_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; +} + +/* 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->handle_request_done = mod_setenv_reset; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_simple_vhost.c b/src/mod_simple_vhost.c new file mode 100644 index 0000000..8f81384 --- /dev/null +++ b/src/mod_simple_vhost.c @@ -0,0 +1,281 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "stat_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; + + unsigned short debug; +} 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 }, + { "simple-vhost.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, 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(); + + s->debug = 0; + + cv[0].destination = s->server_root; + cv[1].destination = s->default_host; + cv[2].destination = s->document_root; + cv[3].destination = &(s->debug); + + + 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) { + stat_cache_entry *sce = NULL; + + 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_ERROR == stat_cache_get_entry(srv, con, out, &sce)) { + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), out); + } + return -1; + } else if (!S_ISDIR(sce->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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(server_root); + PATCH(default_host); + PATCH(document_root); + + PATCH(docroot_cache_key); + PATCH(docroot_cache_value); + PATCH(docroot_cache_servername); + + PATCH(debug); + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.debug"))) { + PATCH(debug); + } + } + } + + return 0; +} +#undef PATCH + +static handler_t mod_simple_vhost_docroot(server *srv, connection *con, void *p_data) { + plugin_data *p = p_data; + + /* + * cache the last successfull translation from hostname (authority) to docroot + * - this saves us a stat() call + * + */ + + mod_simple_vhost_patch_connection(srv, con, p); + + 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 0000000..a3fa186 --- /dev/null +++ b/src/mod_skeleton.c @@ -0,0 +1,210 @@ +#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 + +/** + * 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]; + + if (!s) continue; + + 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 = calloc(1, 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->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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(match); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#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_patch_connection(srv, con, p); + + 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 0000000..b6a19d7 --- /dev/null +++ b/src/mod_ssi.c @@ -0,0 +1,1086 @@ +#include <sys/types.h> + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <unistd.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "stat_cache.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 + +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.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 = calloc(1, 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->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++) { + char c = '_'; + if (light_isalpha(ds->key->ptr[j])) { + /* upper-case */ + c = ds->key->ptr[j] & ~32; + } else if (light_isdigit(ds->key->ptr[j])) { + /* copy */ + c = ds->key->ptr[j]; + } + srv->tmp_buf->ptr[srv->tmp_buf->used++] = c; + } + 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); + + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION); + 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; + stat_cache_entry *sce = 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; + } + + stat_cache_get_entry(srv, con, con->physical.path, &sce); + + 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(sce->st.st_uid))) { + buffer_copy_long(b, sce->st.st_uid); + } else { + buffer_copy_string(b, pw->pw_name); + } +#else + buffer_copy_long(b, sce->st.st_uid); +#endif + break; + } + case SSI_ECHO_LAST_MODIFIED: { + time_t t = sce->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_path(p->stat_fn); + buffer_path_simplify(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)) { + int toread; + /* read everything from client and paste it into the output */ + + while(1) { + if (ioctl(from_exec_fds[0], FIONREAD, &toread)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "unexpected end-of-file (perhaps the ssi-exec process died)"); + return -1; + } + + if (toread > 0) { + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_prepare_copy(b, toread + 1); + + if ((r = read(from_exec_fds[0], b->ptr, b->size - 1)) < 0) { + /* read failed */ + break; + } else { + b->used = r; + b->ptr[b->used++] = '\0'; + } + } else { + break; + } + } + } 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(ssi_extension); + + /* 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]; + s = p->config_storage[i]; + + /* 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; +} +#undef PATCH + +URIHANDLER_FUNC(mod_ssi_physical_path) { + plugin_data *p = p_d; + size_t k; + + if (con->physical.path->used == 0) return HANDLER_GO_ON; + + mod_ssi_patch_connection(srv, con, p); + + 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_subrequest_start = 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 0000000..80f03ed --- /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 0000000..98959ab --- /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 0000000..b484f78 --- /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.c b/src/mod_ssi_exprparser.c new file mode 100644 index 0000000..65ec4dc --- /dev/null +++ b/src/mod_ssi_exprparser.c @@ -0,0 +1,951 @@ +/* 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> +#line 6 "./mod_ssi_exprparser.y" + +#include <assert.h> +#include <string.h> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "mod_ssi_expr.h" +#include "buffer.h" + +#line 18 "mod_ssi_exprparser.c" +/* 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. +** ssiexprparserTOKENTYPE 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 ssiexprparserTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. +** ssiexprparserARG_SDECL A static variable declaration for the %extra_argument +** ssiexprparserARG_PDECL A parameter declaration for the %extra_argument +** ssiexprparserARG_STORE Code to store %extra_argument into yypParser +** ssiexprparserARG_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 YYCODETYPE unsigned char +#define YYNOCODE 20 +#define YYACTIONTYPE unsigned char +#define ssiexprparserTOKENTYPE buffer * +typedef union { + ssiexprparserTOKENTYPE yy0; + int yy8; + buffer * yy19; + ssi_val_t * yy29; + int yy39; +} YYMINORTYPE; +#define YYSTACKDEPTH 100 +#define ssiexprparserARG_SDECL ssi_ctx_t *ctx; +#define ssiexprparserARG_PDECL ,ssi_ctx_t *ctx +#define ssiexprparserARG_FETCH ssi_ctx_t *ctx = yypParser->ctx +#define ssiexprparserARG_STORE yypParser->ctx = ctx +#define YYNSTATE 23 +#define YYNRULE 16 +#define YYERRORSYMBOL 13 +#define YYERRSYMDT yy39 +#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. +*/ +static YYACTIONTYPE yy_action[] = { + /* 0 */ 5, 7, 17, 18, 22, 20, 21, 19, 2, 14, + /* 10 */ 1, 23, 40, 9, 11, 3, 16, 2, 14, 12, + /* 20 */ 4, 14, 5, 7, 6, 14, 7, 8, 14, 10, + /* 30 */ 14, 13, 37, 37, 15, +}; +static YYCODETYPE yy_lookahead[] = { + /* 0 */ 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, + /* 10 */ 16, 0, 18, 9, 10, 17, 12, 14, 15, 16, + /* 20 */ 14, 15, 1, 2, 14, 15, 2, 14, 15, 14, + /* 30 */ 15, 11, 19, 19, 12, +}; +#define YY_SHIFT_USE_DFLT (-2) +static signed char yy_shift_ofst[] = { + /* 0 */ 4, 11, -1, 4, 21, 4, 24, 4, -2, 4, + /* 10 */ -2, 4, 20, -2, 22, -2, -2, -2, -2, -2, + /* 20 */ -2, -2, -2, +}; +#define YY_REDUCE_USE_DFLT (-7) +static signed char yy_reduce_ofst[] = { + /* 0 */ -6, -7, -2, 6, -7, 10, -7, 13, -7, 15, + /* 10 */ -7, 3, -7, -7, -7, -7, -7, -7, -7, -7, + /* 20 */ -7, -7, -7, +}; +static YYACTIONTYPE yy_default[] = { + /* 0 */ 39, 39, 25, 39, 24, 39, 26, 39, 27, 39, + /* 10 */ 28, 39, 39, 29, 30, 32, 31, 33, 34, 35, + /* 20 */ 36, 37, 38, +}; +#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 */ + ssiexprparserARG_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 ssiexprparserTrace(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[] = { + "$", "AND", "OR", "EQ", + "NE", "GT", "GE", "LT", + "LE", "NOT", "LPARAN", "RPARAN", + "VALUE", "error", "expr", "value", + "exprline", "cond", "input", +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *yyRuleName[] = { + /* 0 */ "input ::= exprline", + /* 1 */ "exprline ::= expr cond expr", + /* 2 */ "exprline ::= expr", + /* 3 */ "expr ::= expr AND expr", + /* 4 */ "expr ::= expr OR expr", + /* 5 */ "expr ::= NOT expr", + /* 6 */ "expr ::= LPARAN exprline RPARAN", + /* 7 */ "expr ::= value", + /* 8 */ "value ::= VALUE", + /* 9 */ "value ::= value VALUE", + /* 10 */ "cond ::= EQ", + /* 11 */ "cond ::= NE", + /* 12 */ "cond ::= LE", + /* 13 */ "cond ::= GE", + /* 14 */ "cond ::= LT", + /* 15 */ "cond ::= GT", +}; +#endif /* NDEBUG */ + +/* +** This function returns the symbolic name associated with a token +** value. +*/ +const char *ssiexprparserTokenName(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 ssiexprparser and ssiexprparserFree. +*/ +void *ssiexprparserAlloc(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. + */ + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: +#line 24 "./mod_ssi_exprparser.y" +{ buffer_free((yypminor->yy0)); } +#line 350 "mod_ssi_exprparser.c" + break; + 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 ssiexprparserAlloc. +** <li> A pointer to a function used to reclaim memory obtained +** from malloc. +** </ul> +*/ +void ssiexprparserFree( + 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 ){ + ssiexprparserARG_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 */ + ssiexprparserARG_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[] = { + { 18, 1 }, + { 16, 3 }, + { 16, 1 }, + { 14, 3 }, + { 14, 3 }, + { 14, 2 }, + { 14, 3 }, + { 14, 1 }, + { 15, 1 }, + { 15, 2 }, + { 17, 1 }, + { 17, 1 }, + { 17, 1 }, + { 17, 1 }, + { 17, 1 }, + { 17, 1 }, +}; + +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 */ + ssiexprparserARG_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; + */ + case 0: +#line 31 "./mod_ssi_exprparser.y" +{ + ctx->val.bo = ssi_val_tobool(yymsp[0].minor.yy29); + ctx->val.type = SSI_TYPE_BOOL; + + ssi_val_free(yymsp[0].minor.yy29); +} +#line 586 "mod_ssi_exprparser.c" + break; + case 1: +#line 38 "./mod_ssi_exprparser.y" +{ + int cmp; + + if (yymsp[-2].minor.yy29->type == SSI_TYPE_STRING && + yymsp[0].minor.yy29->type == SSI_TYPE_STRING) { + cmp = strcmp(yymsp[-2].minor.yy29->str->ptr, yymsp[0].minor.yy29->str->ptr); + } else { + cmp = ssi_val_tobool(yymsp[-2].minor.yy29) - ssi_val_tobool(yymsp[0].minor.yy29); + } + + yygotominor.yy29 = yymsp[-2].minor.yy29; + + switch(yymsp[-1].minor.yy8) { + case SSI_COND_EQ: yygotominor.yy29->bo = (cmp == 0) ? 1 : 0; break; + case SSI_COND_NE: yygotominor.yy29->bo = (cmp != 0) ? 1 : 0; break; + case SSI_COND_GE: yygotominor.yy29->bo = (cmp >= 0) ? 1 : 0; break; + case SSI_COND_GT: yygotominor.yy29->bo = (cmp > 0) ? 1 : 0; break; + case SSI_COND_LE: yygotominor.yy29->bo = (cmp <= 0) ? 1 : 0; break; + case SSI_COND_LT: yygotominor.yy29->bo = (cmp < 0) ? 1 : 0; break; + } + + yygotominor.yy29->type = SSI_TYPE_BOOL; + + ssi_val_free(yymsp[0].minor.yy29); +} +#line 615 "mod_ssi_exprparser.c" + break; + case 2: +#line 63 "./mod_ssi_exprparser.y" +{ + yygotominor.yy29 = yymsp[0].minor.yy29; +} +#line 622 "mod_ssi_exprparser.c" + break; + case 3: +#line 66 "./mod_ssi_exprparser.y" +{ + int e; + + e = ssi_val_tobool(yymsp[-2].minor.yy29) && ssi_val_tobool(yymsp[0].minor.yy29); + + yygotominor.yy29 = yymsp[-2].minor.yy29; + yygotominor.yy29->bo = e; + yygotominor.yy29->type = SSI_TYPE_BOOL; + ssi_val_free(yymsp[0].minor.yy29); +} +#line 636 "mod_ssi_exprparser.c" + yy_destructor(1,&yymsp[-1].minor); + break; + case 4: +#line 77 "./mod_ssi_exprparser.y" +{ + int e; + + e = ssi_val_tobool(yymsp[-2].minor.yy29) || ssi_val_tobool(yymsp[0].minor.yy29); + + yygotominor.yy29 = yymsp[-2].minor.yy29; + yygotominor.yy29->bo = e; + yygotominor.yy29->type = SSI_TYPE_BOOL; + ssi_val_free(yymsp[0].minor.yy29); +} +#line 651 "mod_ssi_exprparser.c" + yy_destructor(2,&yymsp[-1].minor); + break; + case 5: +#line 88 "./mod_ssi_exprparser.y" +{ + int e; + + e = !ssi_val_tobool(yymsp[0].minor.yy29); + + yygotominor.yy29 = yymsp[0].minor.yy29; + yygotominor.yy29->bo = e; + yygotominor.yy29->type = SSI_TYPE_BOOL; +} +#line 665 "mod_ssi_exprparser.c" + yy_destructor(9,&yymsp[-1].minor); + break; + case 6: +#line 97 "./mod_ssi_exprparser.y" +{ + yygotominor.yy29 = yymsp[-1].minor.yy29; +} +#line 673 "mod_ssi_exprparser.c" + yy_destructor(10,&yymsp[-2].minor); + yy_destructor(11,&yymsp[0].minor); + break; + case 7: +#line 101 "./mod_ssi_exprparser.y" +{ + yygotominor.yy29 = ssi_val_init(); + yygotominor.yy29->str = yymsp[0].minor.yy19; + yygotominor.yy29->type = SSI_TYPE_STRING; +} +#line 684 "mod_ssi_exprparser.c" + break; + case 8: +#line 107 "./mod_ssi_exprparser.y" +{ + yygotominor.yy19 = buffer_init_string(yymsp[0].minor.yy0->ptr); +} +#line 691 "mod_ssi_exprparser.c" + break; + case 9: +#line 111 "./mod_ssi_exprparser.y" +{ + yygotominor.yy19 = yymsp[-1].minor.yy19; + buffer_append_string_buffer(yygotominor.yy19, yymsp[0].minor.yy0); +} +#line 699 "mod_ssi_exprparser.c" + break; + case 10: +#line 116 "./mod_ssi_exprparser.y" +{ yygotominor.yy8 = SSI_COND_EQ; } +#line 704 "mod_ssi_exprparser.c" + yy_destructor(3,&yymsp[0].minor); + break; + case 11: +#line 117 "./mod_ssi_exprparser.y" +{ yygotominor.yy8 = SSI_COND_NE; } +#line 710 "mod_ssi_exprparser.c" + yy_destructor(4,&yymsp[0].minor); + break; + case 12: +#line 118 "./mod_ssi_exprparser.y" +{ yygotominor.yy8 = SSI_COND_LE; } +#line 716 "mod_ssi_exprparser.c" + yy_destructor(8,&yymsp[0].minor); + break; + case 13: +#line 119 "./mod_ssi_exprparser.y" +{ yygotominor.yy8 = SSI_COND_GE; } +#line 722 "mod_ssi_exprparser.c" + yy_destructor(6,&yymsp[0].minor); + break; + case 14: +#line 120 "./mod_ssi_exprparser.y" +{ yygotominor.yy8 = SSI_COND_LT; } +#line 728 "mod_ssi_exprparser.c" + yy_destructor(7,&yymsp[0].minor); + break; + case 15: +#line 121 "./mod_ssi_exprparser.y" +{ yygotominor.yy8 = SSI_COND_GT; } +#line 734 "mod_ssi_exprparser.c" + yy_destructor(5,&yymsp[0].minor); + 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 */ +){ + ssiexprparserARG_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 */ +#line 16 "./mod_ssi_exprparser.y" + + ctx->ok = 0; + +#line 768 "mod_ssi_exprparser.c" + ssiexprparserARG_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 */ +){ + ssiexprparserARG_FETCH; +#define TOKEN (yyminor.yy0) + ssiexprparserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ssiexprparserARG_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 */ + ssiexprparserARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ssiexprparserAlloc" 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 ssiexprparser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ssiexprparserTOKENTYPE yyminor /* The value for the token */ + ssiexprparserARG_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); + ssiexprparserARG_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/mod_ssi_exprparser.h b/src/mod_ssi_exprparser.h new file mode 100644 index 0000000..eb55ea5 --- /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 0000000..c123941 --- /dev/null +++ b/src/mod_ssi_exprparser.y @@ -0,0 +1,121 @@ +%token_prefix TK_ +%token_type {buffer *} +%extra_argument {ssi_ctx_t *ctx} +%name ssiexprparser + +%include { +#include <assert.h> +#include <string.h> +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#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_staticfile.c b/src/mod_staticfile.c new file mode 100644 index 0000000..6fac65b --- /dev/null +++ b/src/mod_staticfile.c @@ -0,0 +1,492 @@ +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "stat_cache.h" +#include "etag.h" +#include "http_chunk.h" +#include "response.h" + +/** + * this is a staticfile for a lighttpd plugin + * + */ + + + +/* plugin config for all request/connections */ + +typedef struct { + array *exclude_ext; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *range_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_staticfile_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->range_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_staticfile_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->exclude_ext); + + free(s); + } + free(p->config_storage); + } + buffer_free(p->range_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_staticfile_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "static-file.exclude-extensions", 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 = calloc(1, 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->exclude_ext = array_init(); + + cv[0].destination = s->exclude_ext; + + 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_staticfile_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(exclude_ext); + + /* 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]; + s = p->config_storage[i]; + + /* 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("static-file.exclude-extensions"))) { + PATCH(exclude_ext); + } + } + } + + return 0; +} +#undef PATCH + +static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { + int multipart = 0; + int error; + off_t start, end; + const char *s, *minus; + char *boundary = "fkj49sn38dcn3"; + data_string *ds; + stat_cache_entry *sce = NULL; + buffer *content_type = NULL; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + SEGFAULT(); + } + + start = 0; + end = sce->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; + } + + for (s = con->request.http_range, error = 0; + !error && *s && NULL != (minus = strchr(s, '-')); ) { + char *err; + off_t la, le; + + if (s == minus) { + /* -<stop> */ + + le = strtoll(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 = sce->st.st_size - 1; + start = sce->st.st_size + le; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = sce->st.st_size - 1; + start = sce->st.st_size + le; + } else { + error = 1; + } + + } else if (*(minus+1) == '\0' || *(minus+1) == ',') { + /* <start>- */ + + la = strtoll(s, &err, 10); + + if (err == minus) { + /* ok */ + + if (*(err + 1) == '\0') { + s = err + 1; + + end = sce->st.st_size - 1; + start = la; + + } else if (*(err + 1) == ',') { + multipart = 1; + s = err + 2; + + end = sce->st.st_size - 1; + start = la; + } else { + error = 1; + } + } else { + /* error */ + error = 1; + } + } else { + /* <start>-<stop> */ + + la = strtoll(s, &err, 10); + + if (err == minus) { + le = strtoll(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 > sce->st.st_size - 1) end = sce->st.st_size - 1; + + if (start > sce->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, sce->st.st_size); + + buffer_append_string(b, "\r\nContent-Type: "); + buffer_append_string_buffer(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 -1; + + 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(p->range_buf, "multipart/byteranges; boundary="); + buffer_append_string(p->range_buf, boundary); + + /* overwrite content-type */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); + } else { + /* add Content-Range-header */ + + buffer_copy_string(p->range_buf, "bytes "); + buffer_append_off_t(p->range_buf, start); + buffer_append_string(p->range_buf, "-"); + buffer_append_off_t(p->range_buf, end); + buffer_append_string(p->range_buf, "/"); + buffer_append_off_t(p->range_buf, sce->st.st_size); + + response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); + } + + /* ok, the file is set-up */ + return 0; +} + +URIHANDLER_FUNC(mod_staticfile_subrequest) { + plugin_data *p = p_d; + size_t k; + int s_len; + stat_cache_entry *sce = NULL; + buffer *mtime; + data_string *ds; + + /* someone else has done a decision for us */ + if (con->http_status != 0) return HANDLER_GO_ON; + if (con->uri.path->used == 0) return HANDLER_GO_ON; + if (con->physical.path->used == 0) return HANDLER_GO_ON; + + /* someone else has handled this request */ + if (con->mode != DIRECT) return HANDLER_GO_ON; + + /* we only handle GET, POST and HEAD */ + switch(con->request.http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_POST: + case HTTP_METHOD_HEAD: + break; + default: + return HANDLER_GO_ON; + } + + mod_staticfile_patch_connection(srv, con, p); + + s_len = con->uri.path->used - 1; + + /* ignore certain extensions */ + for (k = 0; k < p->conf.exclude_ext->used; k++) { + ds = (data_string *)p->conf.exclude_ext->data[k]; + + if (ds->value->used == 0) continue; + + if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) { + return HANDLER_GO_ON; + } + } + + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file"); + } + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + con->http_status = 403; + + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "not a regular file:", con->uri.path, + "->", con->physical.path); + + return HANDLER_FINISHED; + } + + /* we only handline regular files */ + if (!S_ISREG(sce->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, + "->", sce->name); + } + + return HANDLER_FINISHED; + } + + /* mod_compress might set several data directly, don't overwrite them */ + + /* set response content-type, if not set already */ + + if (NULL == array_get_element(con->response.headers, "Content-Type")) { + if (buffer_is_empty(sce->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(sce->content_type)); + } + } + + if (NULL == array_get_element(con->response.headers, "ETag")) { + /* generate e-tag */ + etag_mutate(con->physical.etag, sce->etag); + + response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); + } + response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes")); + + /* prepare header */ + if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) { + mtime = strftime_cache_get(srv, sce->st.st_mtime); + response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); + } else { + mtime = ds->value; + } + + if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) { + return HANDLER_FINISHED; + } else if (con->request.http_range && con->conf.range_requests) { + int do_range_request = 1; + /* check if we have a conditional GET */ + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) { + /* if the value is the same as our ETag, we do a Range-request, + * otherwise a full 200 */ + + if (!buffer_is_equal(ds->value, con->physical.etag)) { + do_range_request = 0; + } + } + + if (do_range_request) { + /* content prepared, I'm done */ + con->file_finished = 1; + + if (0 == http_response_parse_range(srv, con, p)) { + con->http_status = 206; + } + return HANDLER_FINISHED; + } + } + + /* if we are still here, prepare body */ + + /* we add it here for all requests + * the HEAD request will drop it afterwards again + */ + http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size); + + con->file_finished = 1; + + return HANDLER_FINISHED; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_staticfile_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("staticfile"); + + p->init = mod_staticfile_init; + p->handle_subrequest_start = mod_staticfile_subrequest; + p->set_defaults = mod_staticfile_set_defaults; + p->cleanup = mod_staticfile_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_status.c b/src/mod_status.c new file mode 100644 index 0000000..f5a35e9 --- /dev/null +++ b/src/mod_status.c @@ -0,0 +1,841 @@ +#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 <stdio.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; + buffer *statistics_url; + + int sort; +} 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->statistics_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 }, + { "status.enable-sort", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, + { "status.statistics-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 = calloc(1, 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->config_url = buffer_init(); + s->status_url = buffer_init(); + s->sort = 1; + s->statistics_url = buffer_init(); + + cv[0].destination = s->status_url; + cv[1].destination = s->config_url; + cv[2].destination = &(s->sort); + cv[3].destination = s->statistics_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 int mod_status_header_append_sort(buffer *b, void *p_d, const char* key) { + plugin_data *p = p_d; + + if (p->conf.sort) { + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"); + buffer_append_string(b, key); + BUFFER_APPEND_STRING_CONST(b, "<span class=\"sortarrow\"></span></a></th>\n"); + } else { + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">"); + buffer_append_string(b, key); + BUFFER_APPEND_STRING_CONST(b, "</th>\n"); + } + + return 0; +} + +static int mod_status_get_multiplier(double *avg, char *multiplier, int size) { + *multiplier = ' '; + + if (*avg > size) { *avg /= size; *multiplier = 'k'; } + if (*avg > size) { *avg /= size; *multiplier = 'M'; } + if (*avg > size) { *avg /= size; *multiplier = 'G'; } + if (*avg > size) { *avg /= size; *multiplier = 'T'; } + if (*avg > size) { *avg /= size; *multiplier = 'P'; } + if (*avg > size) { *avg /= size; *multiplier = 'E'; } + if (*avg > size) { *avg /= size; *multiplier = 'Z'; } + if (*avg > size) { *avg /= size; *multiplier = 'Y'; } + + return 0; +} + +static handler_t mod_status_handle_server_status_html(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" + " 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" + " th.status { background-color: black; color: white; font-weight: bold; }\n" + " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n" + " span.sortarrow { color: white; text-decoration: none; }\n" + " </style>\n"); + + if (p->conf.sort) { + BUFFER_APPEND_STRING_CONST(b, + "<script type=\"text/javascript\">\n" + "// <!--\n" + "var sort_column;\n" + "var prev_span = null;\n"); + + BUFFER_APPEND_STRING_CONST(b, + "function get_inner_text(el) {\n" + " if((typeof el == 'string')||(typeof el == 'undefined'))\n" + " return el;\n" + " if(el.innerText)\n" + " return el.innerText;\n" + " else {\n" + " var str = \"\";\n" + " var cs = el.childNodes;\n" + " var l = cs.length;\n" + " for (i=0;i<l;i++) {\n" + " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n" + " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n" + " }\n" + " }\n" + " return str;\n" + "}\n"); + + BUFFER_APPEND_STRING_CONST(b, + "function sortfn(a,b) {\n" + " var at = get_inner_text(a.cells[sort_column]);\n" + " var bt = get_inner_text(b.cells[sort_column]);\n" + " if (a.cells[sort_column].className == 'int') {\n" + " return parseInt(at)-parseInt(bt);\n" + " } else {\n" + " aa = at.toLowerCase();\n" + " bb = bt.toLowerCase();\n" + " if (aa==bb) return 0;\n" + " else if (aa<bb) return -1;\n" + " else return 1;\n" + " }\n" + "}\n"); + + BUFFER_APPEND_STRING_CONST(b, + "function resort(lnk) {\n" + " var span = lnk.childNodes[1];\n" + " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n" + " var rows = new Array();\n" + " for (j=1;j<table.rows.length;j++)\n" + " rows[j-1] = table.rows[j];\n" + " sort_column = lnk.parentNode.cellIndex;\n" + " rows.sort(sortfn);\n"); + + BUFFER_APPEND_STRING_CONST(b, + " if (prev_span != null) prev_span.innerHTML = '';\n" + " if (span.getAttribute('sortdir')=='down') {\n" + " span.innerHTML = '↑';\n" + " span.setAttribute('sortdir','up');\n" + " rows.reverse();\n" + " } else {\n" + " span.innerHTML = '↓';\n" + " span.setAttribute('sortdir','down');\n" + " }\n" + " for (i=0;i<rows.length;i++)\n" + " table.tBodies[0].appendChild(rows[i]);\n" + " prev_span = span;\n" + "}\n" + "// -->\n" + "</script>\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; + + mod_status_get_multiplier(&avg, &multiplier, 1000); + + 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; + + mod_status_get_multiplier(&avg, &multiplier, 1024); + + sprintf(buf, "%.2f", avg); + buffer_append_string(b, buf); + 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 (since start)</th></tr>\n"); + + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">"); + avg = p->abs_requests / (srv->cur_ts - srv->startup_ts); + + mod_status_get_multiplier(&avg, &multiplier, 1000); + + 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"); + + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">"); + avg = p->abs_traffic_out / (srv->cur_ts - srv->startup_ts); + + mod_status_get_multiplier(&avg, &multiplier, 1024); + + sprintf(buf, "%.2f", avg); + buffer_append_string(b, buf); + 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, "<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\">"); + + mod_status_get_multiplier(&avg, &multiplier, 1000); + + 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\">"); + + mod_status_get_multiplier(&avg, &multiplier, 1024); + + sprintf(buf, "%.2f", avg); + buffer_append_string(b, buf); + 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>"); + mod_status_header_append_sort(b, p_d, "Client IP"); + mod_status_header_append_sort(b, p_d, "Read"); + mod_status_header_append_sort(b, p_d, "Written"); + mod_status_header_append_sort(b, p_d, "State"); + mod_status_header_append_sort(b, p_d, "Time"); + mod_status_header_append_sort(b, p_d, "Host"); + mod_status_header_append_sort(b, p_d, "URI"); + mod_status_header_append_sort(b, p_d, "File"); + 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_queue->bytes_in); + 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\">"); + + if (buffer_is_empty(c->server_name)) { + buffer_append_string_buffer(b, c->uri.authority); + } + else { + buffer_append_string_buffer(b, c->server_name); + } + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); + + if (!buffer_is_empty(c->uri.path)) { + buffer_append_string_encoded(b, CONST_BUF_LEN(c->uri.path), ENCODING_HTML); + } + + 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")); + + return 0; +} + + +static handler_t mod_status_handle_server_status_text(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + buffer *b; + double avg; + time_t ts; + + b = chunkqueue_get_append_buffer(con->write_queue); + + /* output total number of requests */ + BUFFER_APPEND_STRING_CONST(b, "Total Accesses: "); + avg = p->abs_requests; + buffer_append_long(b, avg); + BUFFER_APPEND_STRING_CONST(b, "\n"); + + /* output total traffic out in kbytes */ + BUFFER_APPEND_STRING_CONST(b, "Total kBytes: "); + avg = p->abs_traffic_out / 1024; + buffer_append_long(b, avg); + BUFFER_APPEND_STRING_CONST(b, "\n"); + + /* output uptime */ + BUFFER_APPEND_STRING_CONST(b, "Uptime: "); + ts = srv->cur_ts - srv->startup_ts; + buffer_append_long(b, ts); + BUFFER_APPEND_STRING_CONST(b, "\n"); + + /* output busy servers */ + BUFFER_APPEND_STRING_CONST(b, "BusyServers: "); + buffer_append_long(b, srv->conns->used); + BUFFER_APPEND_STRING_CONST(b, "\n"); + + /* set text/plain output */ + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); + + return 0; +} + +static handler_t mod_status_handle_server_statistics(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + buffer *b, *m = p->module_list; + size_t i; + array *st = srv->status; + + if (0 == st->used) { + /* we have nothing to send */ + con->http_status = 204; + con->file_finished = 1; + + return HANDLER_FINISHED; + } + + b = chunkqueue_get_append_buffer(con->write_queue); + + for (i = 0; i < st->used; i++) { + size_t ndx = st->sorted[i]; + + buffer_append_string_buffer(b, st->data[ndx]->key); + buffer_append_string(b, ": "); + buffer_append_long(b, ((data_integer *)(st->data[ndx]))->value); + buffer_append_string(b, "\n"); + } + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain")); + + con->http_status = 200; + con->file_finished = 1; + + return HANDLER_FINISHED; +} + + +static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) { + + if (buffer_is_equal_string(con->uri.query, CONST_STR_LEN("auto"))) { + mod_status_handle_server_status_text(srv, con, p_d); + } else { + mod_status_handle_server_status_html(srv, con, p_d); + } + + 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, "RegEx Conditionals", "enabled"); +#else + mod_status_row_append(b, "RegEx Conditionals", "disabled - pcre 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"); + + 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_status_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(status_url); + PATCH(config_url); + PATCH(sort); + PATCH(statistics_url); + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.enable-sort"))) { + PATCH(sort); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.statistics-url"))) { + PATCH(statistics_url); + } + } + } + + return 0; +} + +static handler_t mod_status_handler(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + mod_status_patch_connection(srv, con, p); + + 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); + } else if (!buffer_is_empty(p->conf.statistics_url) && + buffer_is_equal(p->conf.statistics_url, con->uri.path)) { + return mod_status_handle_server_statistics(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_trigger_b4_dl.c b/src/mod_trigger_b4_dl.c new file mode 100644 index 0000000..8281ec0 --- /dev/null +++ b/src/mod_trigger_b4_dl.c @@ -0,0 +1,586 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" +#include "response.h" +#include "inet_ntop_cache.h" + +#if defined(HAVE_GDBM_H) +#include <gdbm.h> +#endif + +#if defined(HAVE_PCRE_H) +#include <pcre.h> +#endif + +#if defined(HAVE_MEMCACHE_H) +#include <memcache.h> +#endif + +/** + * this is a trigger_b4_dl for a lighttpd plugin + * + */ + +/* plugin config for all request/connections */ + +typedef struct { + buffer *db_filename; + + buffer *trigger_url; + buffer *download_url; + buffer *deny_url; + + array *mc_hosts; + buffer *mc_namespace; +#if defined(HAVE_PCRE_H) + pcre *trigger_regex; + pcre *download_regex; +#endif +#if defined(HAVE_GDBM_H) + GDBM_FILE db; +#endif + +#if defined(HAVE_MEMCACHE_H) + struct memcache *mc; +#endif + + unsigned short trigger_timeout; + unsigned short debug; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_trigger_b4_dl_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_trigger_b4_dl_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) continue; + + buffer_free(s->db_filename); + buffer_free(s->download_url); + buffer_free(s->trigger_url); + buffer_free(s->deny_url); + + buffer_free(s->mc_namespace); + array_free(s->mc_hosts); + +#if defined(HAVE_PCRE_H) + if (s->trigger_regex) pcre_free(s->trigger_regex); + if (s->download_regex) pcre_free(s->download_regex); +#endif +#if defined(HAVE_GDBM_H) + if (s->db) gdbm_close(s->db); +#endif +#if defined(HAVE_MEMCACHE_H) + if (s->mc) mc_free(s->mc); +#endif + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_trigger_b4_dl_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + + config_values_t cv[] = { + { "trigger-before-download.gdbm-filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "trigger-before-download.trigger-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "trigger-before-download.download-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "trigger-before-download.deny-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { "trigger-before-download.trigger-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ + { "trigger-before-download.memcache-hosts", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ + { "trigger-before-download.memcache-namespace", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ + { "trigger-before-download.debug", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; +#if defined(HAVE_PCRE_H) + const char *errptr; + int erroff; +#endif + + s = calloc(1, sizeof(plugin_config)); + s->db_filename = buffer_init(); + s->download_url = buffer_init(); + s->trigger_url = buffer_init(); + s->deny_url = buffer_init(); + s->mc_hosts = array_init(); + s->mc_namespace = buffer_init(); + + cv[0].destination = s->db_filename; + cv[1].destination = s->trigger_url; + cv[2].destination = s->download_url; + cv[3].destination = s->deny_url; + cv[4].destination = &(s->trigger_timeout); + cv[5].destination = s->mc_hosts; + cv[6].destination = s->mc_namespace; + cv[7].destination = &(s->debug); + + 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 defined(HAVE_GDBM_H) + if (!buffer_is_empty(s->db_filename)) { + if (NULL == (s->db = gdbm_open(s->db_filename->ptr, 4096, GDBM_WRCREAT | GDBM_NOLOCK, S_IRUSR | S_IWUSR, 0))) { + log_error_write(srv, __FILE__, __LINE__, "s", + "gdbm-open failed"); + return HANDLER_ERROR; + } + } +#endif +#if defined(HAVE_PCRE_H) + if (!buffer_is_empty(s->download_url)) { + if (NULL == (s->download_regex = pcre_compile(s->download_url->ptr, + 0, &errptr, &erroff, NULL))) { + + log_error_write(srv, __FILE__, __LINE__, "sbss", + "compiling regex for download-url failed:", + s->download_url, "pos:", erroff); + return HANDLER_ERROR; + } + } + + if (!buffer_is_empty(s->trigger_url)) { + if (NULL == (s->trigger_regex = pcre_compile(s->trigger_url->ptr, + 0, &errptr, &erroff, NULL))) { + + log_error_write(srv, __FILE__, __LINE__, "sbss", + "compiling regex for trigger-url failed:", + s->trigger_url, "pos:", erroff); + + return HANDLER_ERROR; + } + } +#endif + + if (s->mc_hosts->used) { +#if defined(HAVE_MEMCACHE_H) + size_t k; + s->mc = mc_new(); + + for (k = 0; k < s->mc_hosts->used; k++) { + data_string *ds = (data_string *)s->mc_hosts->data[k]; + + if (0 != mc_server_add4(s->mc, ds->value->ptr)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "connection to host failed:", + ds->value); + + return HANDLER_ERROR; + } + } +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "memcache support is not compiled in but trigger-before-download.memcache-hosts is set, aborting"); + return HANDLER_ERROR; +#endif + } + + +#if (!defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H)) || !defined(HAVE_PCRE_H) + log_error_write(srv, __FILE__, __LINE__, "s", + "(either gdbm or libmemcache) and pcre are require, but were not found, aborting"); + return HANDLER_ERROR; +#endif + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_trigger_b4_dl_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + +#if defined(HAVE_GDBM) + PATCH(db); +#endif +#if defined(HAVE_PCRE_H) + PATCH(download_regex); + PATCH(trigger_regex); +#endif + PATCH(trigger_timeout); + PATCH(deny_url); + PATCH(mc_namespace); + PATCH(debug); +#if defined(HAVE_MEMCACHE_H) + PATCH(mc); +#endif + + /* 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]; + s = p->config_storage[i]; + + /* 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("trigger-before-download.download-url"))) { +#if defined(HAVE_PCRE_H) + PATCH(download_regex); +#endif + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-url"))) { +# if defined(HAVE_PCRE_H) + PATCH(trigger_regex); +# endif + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.gdbm-filename"))) { +#if defined(HAVE_GDBM_H) + PATCH(db); +#endif + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.trigger-timeout"))) { + PATCH(trigger_timeout); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.debug"))) { + PATCH(debug); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.deny-url"))) { + PATCH(deny_url); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-namespace"))) { + PATCH(mc_namespace); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("trigger-before-download.memcache-hosts"))) { +#if defined(HAVE_MEMCACHE_H) + PATCH(mc); +#endif + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_trigger_b4_dl_uri_handler) { + plugin_data *p = p_d; + const char *remote_ip; + data_string *ds; + +#if defined(HAVE_PCRE_H) + int n; +# define N 10 + int ovec[N * 3]; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_trigger_b4_dl_patch_connection(srv, con, p); + + if (!p->conf.trigger_regex || !p->conf.download_regex) return HANDLER_GO_ON; + +# if !defined(HAVE_GDBM_H) && !defined(HAVE_MEMCACHE_H) + return HANDLER_GO_ON; +# elif defined(HAVE_GDBM_H) && defined(HAVE_MEMCACHE_H) + if (!p->conf.db && !p->conf.mc) return HANDLER_GO_ON; + if (p->conf.db && p->conf.mc) { + /* can't decide which one */ + + return HANDLER_GO_ON; + } +# elif defined(HAVE_GDBM_H) + if (!p->conf.db) return HANDLER_GO_ON; +# else + if (!p->conf.mc) return HANDLER_GO_ON; +# endif + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "X-Forwarded-For"))) { + /* X-Forwarded-For contains the ip behind the proxy */ + + remote_ip = ds->value->ptr; + + /* memcache can't handle spaces */ + } else { + remote_ip = inet_ntop_cache_get_ip(srv, &(con->dst_addr)); + } + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ss", "(debug) remote-ip:", remote_ip); + } + + /* check if URL is a trigger -> insert IP into DB */ + if ((n = pcre_exec(p->conf.trigger_regex, NULL, con->uri.path->ptr, con->uri.path->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 { +# if defined(HAVE_GDBM_H) + if (p->conf.db) { + /* the trigger matched */ + datum key, val; + + key.dptr = (char *)remote_ip; + key.dsize = strlen(remote_ip); + + val.dptr = (char *)&(srv->cur_ts); + val.dsize = sizeof(srv->cur_ts); + + if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "insert failed"); + } + } +# endif +# if defined(HAVE_MEMCACHE_H) + if (p->conf.mc) { + size_t i; + buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace); + buffer_append_string(p->tmp_buf, remote_ip); + + for (i = 0; i < p->tmp_buf->used - 1; i++) { + if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-'; + } + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) triggered IP:", p->tmp_buf); + } + + if (0 != mc_set(p->conf.mc, + CONST_BUF_LEN(p->tmp_buf), + (char *)&(srv->cur_ts), sizeof(srv->cur_ts), + p->conf.trigger_timeout, 0)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "insert failed"); + } + } +# endif + } + + /* check if URL is a download -> check IP in DB, update timestamp */ + if ((n = pcre_exec(p->conf.download_regex, NULL, con->uri.path->ptr, con->uri.path->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 { + /* the download uri matched */ +# if defined(HAVE_GDBM_H) + if (p->conf.db) { + datum key, val; + time_t last_hit; + + key.dptr = (char *)remote_ip; + key.dsize = strlen(remote_ip); + + val = gdbm_fetch(p->conf.db, key); + + if (val.dptr == NULL) { + /* not found, redirect */ + + response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url)); + + con->http_status = 307; + + return HANDLER_FINISHED; + } + + last_hit = *(time_t *)(val.dptr); + + free(val.dptr); + + if (srv->cur_ts - last_hit > p->conf.trigger_timeout) { + /* found, but timeout, redirect */ + + response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url)); + con->http_status = 307; + + if (p->conf.db) { + if (0 != gdbm_delete(p->conf.db, key)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "delete failed"); + } + } + + return HANDLER_FINISHED; + } + + val.dptr = (char *)&(srv->cur_ts); + val.dsize = sizeof(srv->cur_ts); + + if (0 != gdbm_store(p->conf.db, key, val, GDBM_REPLACE)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "insert failed"); + } + } +# endif + +# if defined(HAVE_MEMCACHE_H) + if (p->conf.mc) { + void *r; + size_t i; + + buffer_copy_string_buffer(p->tmp_buf, p->conf.mc_namespace); + buffer_append_string(p->tmp_buf, remote_ip); + + for (i = 0; i < p->tmp_buf->used - 1; i++) { + if (p->tmp_buf->ptr[i] == ' ') p->tmp_buf->ptr[i] = '-'; + } + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sb", "(debug) checking IP:", p->tmp_buf); + } + + /** + * + * memcached is do expiration for us, as long as we can fetch it every thing is ok + * and the timestamp is updated + * + */ + if (NULL == (r = mc_aget(p->conf.mc, + CONST_BUF_LEN(p->tmp_buf) + ))) { + + response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->conf.deny_url)); + + con->http_status = 307; + + return HANDLER_FINISHED; + } + + free(r); + + /* set a new timeout */ + if (0 != mc_set(p->conf.mc, + CONST_BUF_LEN(p->tmp_buf), + (char *)&(srv->cur_ts), sizeof(srv->cur_ts), + p->conf.trigger_timeout, 0)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "insert failed"); + } + } +# endif + } + +#else + UNUSED(srv); + UNUSED(con); + UNUSED(p_d); +#endif + + return HANDLER_GO_ON; +} + +#if defined(HAVE_GDBM_H) +TRIGGER_FUNC(mod_trigger_b4_dl_handle_trigger) { + plugin_data *p = p_d; + size_t i; + + /* check DB each minute */ + if (srv->cur_ts % 60 != 0) return HANDLER_GO_ON; + + /* cleanup */ + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + datum key, val, okey; + + if (!s->db) continue; + + okey.dptr = NULL; + + /* according to the manual this loop + delete does delete all entries on its way + * + * we don't care as the next round will remove them. We don't have to perfect here. + */ + for (key = gdbm_firstkey(s->db); key.dptr; key = gdbm_nextkey(s->db, okey)) { + time_t last_hit; + if (okey.dptr) { + free(okey.dptr); + okey.dptr = NULL; + } + + val = gdbm_fetch(s->db, key); + + last_hit = *(time_t *)(val.dptr); + + free(val.dptr); + + if (srv->cur_ts - last_hit > s->trigger_timeout) { + gdbm_delete(s->db, key); + } + + okey = key; + } + if (okey.dptr) free(okey.dptr); + + /* reorg once a day */ + if ((srv->cur_ts % (60 * 60 * 24) != 0)) gdbm_reorganize(s->db); + } + return HANDLER_GO_ON; +} +#endif + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_trigger_b4_dl_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("trigger_b4_dl"); + + p->init = mod_trigger_b4_dl_init; + p->handle_uri_clean = mod_trigger_b4_dl_uri_handler; + p->set_defaults = mod_trigger_b4_dl_set_defaults; +#if defined(HAVE_GDBM_H) + p->handle_trigger = mod_trigger_b4_dl_handle_trigger; +#endif + p->cleanup = mod_trigger_b4_dl_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_userdir.c b/src/mod_userdir.c new file mode 100644 index 0000000..9612fa8 --- /dev/null +++ b/src/mod_userdir.c @@ -0,0 +1,294 @@ +#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" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +/* plugin config for all request/connections */ +typedef struct { + array *exclude_user; + array *include_user; + buffer *path; + buffer *basepath; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *username; + buffer *temp_path; + + 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(); + p->temp_path = 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); + buffer_free(s->basepath); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->username); + buffer_free(p->temp_path); + + 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 */ + { "userdir.basepath", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, 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->exclude_user = array_init(); + s->include_user = array_init(); + s->path = buffer_init(); + s->basepath = buffer_init(); + + cv[0].destination = s->path; + cv[1].destination = s->exclude_user; + cv[2].destination = s->include_user; + cv[3].destination = s->basepath; + + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(path); + PATCH(exclude_user); + PATCH(include_user); + PATCH(basepath); + + /* 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]; + s = p->config_storage[i]; + + /* 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); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.basepath"))) { + PATCH(basepath); + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_userdir_docroot_handler) { + plugin_data *p = p_d; + int uri_len; + size_t k; + char *rel_url; +#ifdef HAVE_PWD_H + struct passwd *pwd = NULL; +#endif + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_userdir_patch_connection(srv, con, p); + + 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; + } + + /* /~/ is a empty username, catch it directly */ + if (0 == rel_url - (con->uri.path->ptr + 2)) { + return HANDLER_GO_ON; + } + + buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2)); + + if (buffer_is_empty(p->conf.basepath) +#ifdef HAVE_PWD_H + && NULL == (pwd = getpwnam(p->username->ptr)) +#endif + ) { + /* 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 include list */ + found_user = 1; + break; + } + } + + if (!found_user) return HANDLER_GO_ON; + } + + /* we build the physical path */ + + if (buffer_is_empty(p->conf.basepath)) { +#ifdef HAVE_PWD_H + buffer_copy_string(p->temp_path, pwd->pw_dir); +#endif + } else { + char *cp; + /* check if the username is valid + * a request for /~../ should lead to a directory traversal + * limiting to [-_a-z0-9.] should fix it */ + + for (cp = p->username->ptr; *cp; cp++) { + char c = *cp; + + if (!(c == '-' || + c == '_' || + c == '.' || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9'))) { + + return HANDLER_GO_ON; + } + } + + buffer_copy_string_buffer(p->temp_path, p->conf.basepath); + BUFFER_APPEND_SLASH(p->temp_path); + buffer_append_string_buffer(p->temp_path, p->username); + } + BUFFER_APPEND_SLASH(p->temp_path); + buffer_append_string_buffer(p->temp_path, p->conf.path); + + if (buffer_is_empty(p->conf.basepath)) { + struct stat st; + int ret; + + ret = stat(p->temp_path->ptr, &st); + if (ret < 0 || S_ISDIR(st.st_mode) != 1) { + return HANDLER_GO_ON; + } + } + + BUFFER_APPEND_SLASH(p->temp_path); + buffer_append_string(p->temp_path, rel_url + 1); /* skip the / */ + buffer_copy_string_buffer(con->physical.path, p->temp_path); + + buffer_reset(p->temp_path); + + return HANDLER_GO_ON; +} + +/* 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_physical = 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 0000000..d3314c2 --- /dev/null +++ b/src/mod_usertrack.c @@ -0,0 +1,270 @@ +#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.h" +#endif + +/* plugin config for all request/connections */ + +typedef struct { + buffer *cookie_name; + buffer *cookie_domain; + unsigned short cookie_max_age; +} 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); + buffer_free(s->cookie_domain); + + 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.cookie-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "usertrack.cookie-max-age", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "usertrack.cookie-domain", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + + { "usertrack.cookiename", NULL, T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, 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->cookie_name = buffer_init(); + s->cookie_domain = buffer_init(); + s->cookie_max_age = 0; + + cv[0].destination = s->cookie_name; + cv[1].destination = &(s->cookie_max_age); + cv[2].destination = s->cookie_domain; + + 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 (buffer_is_empty(s->cookie_name)) { + 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.cookie-name:", + s->cookie_name); + + return HANDLER_ERROR; + } + } + } + + if (!buffer_is_empty(s->cookie_domain)) { + size_t j; + for (j = 0; j < s->cookie_domain->used - 1; j++) { + char c = s->cookie_domain->ptr[j]; + if (c <= 32 || c >= 127 || c == '"' || c == '\\') { + log_error_write(srv, __FILE__, __LINE__, "sb", + "invalid character in usertrack.cookie-domain:", + s->cookie_domain); + + 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) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(cookie_name); + PATCH(cookie_domain); + PATCH(cookie_max_age); + + /* 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]; + s = p->config_storage[i]; + + /* 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.cookie-name"))) { + PATCH(cookie_name); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-max-age"))) { + PATCH(cookie_max_age); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-domain"))) { + PATCH(cookie_domain); + } + } + } + + 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]; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_usertrack_patch_connection(srv, con, p); + + 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_encoded(ds->value, (char *)h, 16, ENCODING_HEX); + buffer_append_string(ds->value, "; Path=/"); + buffer_append_string(ds->value, "; Version=1"); + + if (!buffer_is_empty(p->conf.cookie_domain)) { + buffer_append_string(ds->value, "; Domain="); + buffer_append_string_encoded(ds->value, CONST_BUF_LEN(p->conf.cookie_domain), ENCODING_REL_URI); + } + + if (p->conf.cookie_max_age) { + buffer_append_string(ds->value, "; max-age="); + buffer_append_long(ds->value, p->conf.cookie_max_age); + } + + 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/mod_webdav.c b/src/mod_webdav.c new file mode 100644 index 0000000..3306c73 --- /dev/null +++ b/src/mod_webdav.c @@ -0,0 +1,1854 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <assert.h> +#include <sys/mman.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(HAVE_LIBXML_H) && defined(HAVE_SQLITE3_H) +#define USE_PROPPATCH +#include <libxml/tree.h> +#include <libxml/parser.h> + +#include <sqlite3.h> +#endif + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "response.h" + +#include "plugin.h" + +#include "stream.h" +#include "stat_cache.h" + + +/** + * this is a webdav for a lighttpd plugin + * + * at least a very basic one. + * - for now it is read-only and we only support PROPFIND + * + */ + + + +/* plugin config for all request/connections */ + +typedef struct { + unsigned short enabled; + unsigned short is_readonly; + unsigned short log_xml; + + buffer *sqlite_db_name; +#ifdef USE_PROPPATCH + sqlite3 *sql; + sqlite3_stmt *stmt_update_prop; + sqlite3_stmt *stmt_delete_prop; + sqlite3_stmt *stmt_select_prop; + sqlite3_stmt *stmt_select_propnames; + + sqlite3_stmt *stmt_delete_uri; + sqlite3_stmt *stmt_move_uri; + sqlite3_stmt *stmt_copy_uri; +#endif +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + request_uri uri; + physical physical; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_webdav_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + p->uri.scheme = buffer_init(); + p->uri.path_raw = buffer_init(); + p->uri.path = buffer_init(); + p->uri.authority = buffer_init(); + + p->physical.path = buffer_init(); + p->physical.rel_path = buffer_init(); + p->physical.doc_root = buffer_init(); + p->physical.basedir = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_webdav_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) continue; + + buffer_free(s->sqlite_db_name); +#ifdef USE_PROPPATCH + if (s->sql) { + sqlite3_finalize(s->stmt_delete_prop); + sqlite3_finalize(s->stmt_delete_uri); + sqlite3_finalize(s->stmt_copy_uri); + sqlite3_finalize(s->stmt_move_uri); + sqlite3_finalize(s->stmt_update_prop); + sqlite3_finalize(s->stmt_select_prop); + sqlite3_finalize(s->stmt_select_propnames); + sqlite3_close(s->sql); + } +#endif + free(s); + } + free(p->config_storage); + } + + buffer_free(p->uri.scheme); + buffer_free(p->uri.path_raw); + buffer_free(p->uri.path); + buffer_free(p->uri.authority); + + buffer_free(p->physical.path); + buffer_free(p->physical.rel_path); + buffer_free(p->physical.doc_root); + buffer_free(p->physical.basedir); + + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_webdav_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "webdav.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "webdav.is-readonly", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "webdav.sqlite-db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "webdav.log-xml", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, 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->sqlite_db_name = buffer_init(); + + cv[0].destination = &(s->enabled); + cv[1].destination = &(s->is_readonly); + cv[2].destination = s->sqlite_db_name; + cv[3].destination = &(s->log_xml); + + 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 (!buffer_is_empty(s->sqlite_db_name)) { +#ifdef USE_PROPPATCH + const char *next_stmt; + char *err; + + if (SQLITE_OK != sqlite3_open(s->sqlite_db_name->ptr, &(s->sql))) { + log_error_write(srv, __FILE__, __LINE__, "s", "sqlite3_open failed"); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("SELECT value FROM properties WHERE resource = ? AND prop = ? AND ns = ?"), + &(s->stmt_select_prop), &next_stmt)) { + /* prepare failed */ + + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql)); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("SELECT ns, prop FROM properties WHERE resource = ?"), + &(s->stmt_select_propnames), &next_stmt)) { + /* prepare failed */ + + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql)); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_exec(s->sql, + "CREATE TABLE properties (" + " resource TEXT NOT NULL," + " prop TEXT NOT NULL," + " ns TEXT NOT NULL," + " value TEXT NOT NULL," + " PRIMARY KEY(resource, prop, ns))", + NULL, NULL, &err)) { + + if (0 != strcmp(err, "table properties already exists")) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err); + sqlite3_free(err); + + return HANDLER_ERROR; + } + sqlite3_free(err); + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)"), + &(s->stmt_update_prop), &next_stmt)) { + /* prepare failed */ + + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed:", sqlite3_errmsg(s->sql)); + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("DELETE FROM properties WHERE resource = ? AND prop = ? AND ns = ?"), + &(s->stmt_delete_prop), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("DELETE FROM properties WHERE resource = ?"), + &(s->stmt_delete_uri), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("INSERT INTO properties SELECT ?, prop, ns, value FROM properties WHERE resource = ?"), + &(s->stmt_copy_uri), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } + + if (SQLITE_OK != sqlite3_prepare(s->sql, + CONST_STR_LEN("UPDATE properties SET resource = ? WHERE resource = ?"), + &(s->stmt_move_uri), &next_stmt)) { + /* prepare failed */ + log_error_write(srv, __FILE__, __LINE__, "ss", "sqlite3_prepare failed", sqlite3_errmsg(s->sql)); + + return HANDLER_ERROR; + } +#else + log_error_write(srv, __FILE__, __LINE__, "s", "Sorry, no sqlite3 and libxml2 support include, compile with --with-webdav-props"); + return HANDLER_ERROR; +#endif + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_webdav_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(enabled); + PATCH(is_readonly); + PATCH(log_xml); + +#ifdef USE_PROPPATCH + PATCH(sql); + PATCH(stmt_update_prop); + PATCH(stmt_delete_prop); + PATCH(stmt_select_prop); + PATCH(stmt_select_propnames); + + PATCH(stmt_delete_uri); + PATCH(stmt_move_uri); + PATCH(stmt_copy_uri); +#endif + /* 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]; + s = p->config_storage[i]; + + /* 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("webdav.activate"))) { + PATCH(enabled); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.is-readonly"))) { + PATCH(is_readonly); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.log-xml"))) { + PATCH(log_xml); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("webdav.sqlite-db-name"))) { +#ifdef USE_PROPPATCH + PATCH(sql); + PATCH(stmt_update_prop); + PATCH(stmt_delete_prop); + PATCH(stmt_select_prop); + PATCH(stmt_select_propnames); + + PATCH(stmt_delete_uri); + PATCH(stmt_move_uri); + PATCH(stmt_copy_uri); +#endif + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_webdav_uri_handler) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_webdav_patch_connection(srv, con, p); + + if (!p->conf.enabled) return HANDLER_GO_ON; + + switch (con->request.http_method) { + case HTTP_METHOD_OPTIONS: + /* we fake a little bit but it makes MS W2k happy and it let's us mount the volume */ + response_header_overwrite(srv, con, CONST_STR_LEN("DAV"), CONST_STR_LEN("1,2")); + response_header_overwrite(srv, con, CONST_STR_LEN("MS-Author-Via"), CONST_STR_LEN("DAV")); + + if (p->conf.is_readonly) { + response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND")); + } else { + response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("PROPFIND, DELETE, MKCOL, PUT, MOVE, COPY, PROPPATCH")); + } + break; + default: + break; + } + + /* not found */ + return HANDLER_GO_ON; +} +static int webdav_gen_prop_tag(server *srv, connection *con, + char *prop_name, + char *prop_ns, + char *value, + buffer *b) { + + UNUSED(srv); + UNUSED(con); + + if (value) { + buffer_append_string(b,"<"); + buffer_append_string(b, prop_name); + buffer_append_string(b, " xmlns=\""); + buffer_append_string(b, prop_ns); + buffer_append_string(b, "\">"); + + buffer_append_string(b, value); + + buffer_append_string(b,"</"); + buffer_append_string(b, prop_name); + buffer_append_string(b, ">"); + } else { + buffer_append_string(b,"<"); + buffer_append_string(b, prop_name); + buffer_append_string(b, " xmlns=\""); + buffer_append_string(b, prop_ns); + buffer_append_string(b, "\"/>"); + } + + return 0; +} + + +static int webdav_gen_response_status_tag(server *srv, connection *con, physical *dst, int status, buffer *b) { + UNUSED(srv); + + buffer_append_string(b,"<D:response xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"); + + buffer_append_string(b,"<D:href>\n"); + buffer_append_string_buffer(b, dst->rel_path); + buffer_append_string(b,"</D:href>\n"); + buffer_append_string(b,"<D:status>\n"); + + 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, status); + BUFFER_APPEND_STRING_CONST(b, " "); + buffer_append_string(b, get_http_status_name(status)); + + buffer_append_string(b,"</D:status>\n"); + buffer_append_string(b,"</D:response>\n"); + + return 0; +} + +static int webdav_delete_file(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) { + int status = 0; + + /* try to unlink it */ + if (-1 == unlink(dst->path->ptr)) { + switch(errno) { + case EACCES: + case EPERM: + /* 403 */ + status = 403; + break; + default: + status = 501; + break; + } + webdav_gen_response_status_tag(srv, con, dst, status, b); + } else { +#ifdef USE_PROPPATCH + sqlite3_stmt *stmt = p->conf.stmt_delete_uri; + + if (!stmt) { + status = 403; + webdav_gen_response_status_tag(srv, con, dst, status, b); + } else { + sqlite3_reset(stmt); + + /* bind the values to the insert */ + + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); + } + } +#endif + } + + return (status != 0); +} + +static int webdav_delete_dir(server *srv, connection *con, plugin_data *p, physical *dst, buffer *b) { + DIR *dir; + int have_multi_status = 0; + physical d; + + d.path = buffer_init(); + d.rel_path = buffer_init(); + + if (NULL != (dir = opendir(dst->path->ptr))) { + struct dirent *de; + + while(NULL != (de = readdir(dir))) { + struct stat st; + int status = 0; + + if ((de->d_name[0] == '.' && de->d_name[1] == '\0') || + (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { + continue; + /* ignore the parent dir */ + } + + buffer_copy_string_buffer(d.path, dst->path); + BUFFER_APPEND_SLASH(d.path); + buffer_append_string(d.path, de->d_name); + + buffer_copy_string_buffer(d.rel_path, dst->rel_path); + BUFFER_APPEND_SLASH(d.rel_path); + buffer_append_string(d.rel_path, de->d_name); + + /* stat and unlink afterwards */ + if (-1 == stat(d.path->ptr, &st)) { + /* don't about it yet, rmdir will fail too */ + } else if (S_ISDIR(st.st_mode)) { + have_multi_status = webdav_delete_dir(srv, con, p, &d, b); + + /* try to unlink it */ + if (-1 == rmdir(d.path->ptr)) { + switch(errno) { + case EACCES: + case EPERM: + /* 403 */ + status = 403; + break; + default: + status = 501; + break; + } + have_multi_status = 1; + + webdav_gen_response_status_tag(srv, con, &d, status, b); + } else { +#ifdef USE_PROPPATCH + sqlite3_stmt *stmt = p->conf.stmt_delete_uri; + + status = 0; + + if (stmt) { + sqlite3_reset(stmt); + + /* bind the values to the insert */ + + sqlite3_bind_text(stmt, 1, + d.rel_path->ptr, + d.rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); + } + } +#endif + } + } else { + have_multi_status = webdav_delete_file(srv, con, p, &d, b); + } + } + closedir(dir); + + buffer_free(d.path); + buffer_free(d.rel_path); + } + + return have_multi_status; +} + +static int webdav_copy_file(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) { + stream s; + int status = 0, ofd; + + UNUSED(con); + + if (stream_open(&s, src->path)) { + return 403; + } + + if (-1 == (ofd = open(dst->path->ptr, O_WRONLY|O_TRUNC|O_CREAT|(overwrite ? 0 : O_EXCL), 0600))) { + /* opening the destination failed for some reason */ + switch(errno) { + case EEXIST: + status = 412; + break; + case EISDIR: + status = 409; + break; + case ENOENT: + /* at least one part in the middle wasn't existing */ + status = 409; + break; + default: + status = 403; + break; + } + stream_close(&s); + return status; + } + + if (-1 == write(ofd, s.start, s.size)) { + switch(errno) { + case ENOSPC: + status = 507; + break; + default: + status = 403; + break; + } + } + + stream_close(&s); + close(ofd); + +#ifdef USE_PROPPATCH + if (0 == status) { + /* copy worked fine, copy connected properties */ + sqlite3_stmt *stmt = p->conf.stmt_copy_uri; + + if (stmt) { + sqlite3_reset(stmt); + + /* bind the values to the insert */ + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + + sqlite3_bind_text(stmt, 2, + src->rel_path->ptr, + src->rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); + } + } + } +#endif + return status; +} + +static int webdav_copy_dir(server *srv, connection *con, plugin_data *p, physical *src, physical *dst, int overwrite) { + DIR *srcdir; + int status = 0; + + if (NULL != (srcdir = opendir(src->path->ptr))) { + struct dirent *de; + physical s, d; + + s.path = buffer_init(); + s.rel_path = buffer_init(); + + d.path = buffer_init(); + d.rel_path = buffer_init(); + + while (NULL != (de = readdir(srcdir))) { + struct stat st; + + if ((de->d_name[0] == '.' && de->d_name[1] == '\0') || + (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')) { + continue; + } + + buffer_copy_string_buffer(s.path, src->path); + BUFFER_APPEND_SLASH(s.path); + buffer_append_string(s.path, de->d_name); + + buffer_copy_string_buffer(d.path, dst->path); + BUFFER_APPEND_SLASH(d.path); + buffer_append_string(d.path, de->d_name); + + buffer_copy_string_buffer(s.rel_path, src->rel_path); + BUFFER_APPEND_SLASH(s.rel_path); + buffer_append_string(s.rel_path, de->d_name); + + buffer_copy_string_buffer(d.rel_path, dst->rel_path); + BUFFER_APPEND_SLASH(d.rel_path); + buffer_append_string(d.rel_path, de->d_name); + + if (-1 == stat(s.path->ptr, &st)) { + /* why ? */ + } else if (S_ISDIR(st.st_mode)) { + /* a directory */ + if (-1 == mkdir(d.path->ptr, 0700) && + errno != EEXIST) { + /* WTH ? */ + } else { +#ifdef USE_PROPPATCH + sqlite3_stmt *stmt = p->conf.stmt_copy_uri; + + if (0 != (status = webdav_copy_dir(srv, con, p, &s, &d, overwrite))) { + break; + } + /* directory is copied, copy the properties too */ + + if (stmt) { + sqlite3_reset(stmt); + + /* bind the values to the insert */ + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + + sqlite3_bind_text(stmt, 2, + src->rel_path->ptr, + src->rel_path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + /* */ + WP(); + } + } +#endif + } + } else if (S_ISREG(st.st_mode)) { + /* a plain file */ + if (0 != (status = webdav_copy_file(srv, con, p, &s, &d, overwrite))) { + break; + } + } + } + + buffer_free(s.path); + buffer_free(s.rel_path); + buffer_free(d.path); + buffer_free(d.rel_path); + + closedir(srcdir); + } + + return status; +} + +static int webdav_get_live_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, buffer *b) { + stat_cache_entry *sce = NULL; + int found = 0; + + UNUSED(p); + + if (HANDLER_ERROR != (stat_cache_get_entry(srv, con, dst->path, &sce))) { + char ctime_buf[] = "2005-08-18T07:27:16Z"; + char mtime_buf[] = "Thu, 18 Aug 2005 07:27:16 GMT"; + size_t k; + + if (0 == strcmp(prop_name, "resourcetype")) { + if (S_ISDIR(sce->st.st_mode)) { + buffer_append_string(b, "<D:resourcetype><D:collection/></D:resourcetype>"); + found = 1; + } + } else if (0 == strcmp(prop_name, "getcontenttype")) { + if (S_ISDIR(sce->st.st_mode)) { + buffer_append_string(b, "<D:getcontenttype>httpd/unix-directory</D:getcontenttype>"); + found = 1; + } else if(S_ISREG(sce->st.st_mode)) { + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + + if (ds->key->used == 0) continue; + + if (buffer_is_equal_right_len(dst->path, ds->key, ds->key->used - 1)) { + buffer_append_string(b,"<D:getcontenttype>"); + buffer_append_string_buffer(b, ds->value); + buffer_append_string(b, "</D:getcontenttype>"); + found = 1; + + break; + } + } + } + } else if (0 == strcmp(prop_name, "creationdate")) { + buffer_append_string(b, "<D:creationdate ns0:dt=\"dateTime.tz\">"); + strftime(ctime_buf, sizeof(ctime_buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&(sce->st.st_ctime))); + buffer_append_string(b, ctime_buf); + buffer_append_string(b, "</D:creationdate>"); + found = 1; + } else if (0 == strcmp(prop_name, "getlastmodified")) { + buffer_append_string(b,"<D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"); + strftime(mtime_buf, sizeof(mtime_buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(sce->st.st_mtime))); + buffer_append_string(b, mtime_buf); + buffer_append_string(b, "</D:getlastmodified>"); + found = 1; + } else if (0 == strcmp(prop_name, "getcontentlength")) { + buffer_append_string(b,"<D:getcontentlength>"); + buffer_append_off_t(b, sce->st.st_size); + buffer_append_string(b, "</D:getcontentlength>"); + found = 1; + } else if (0 == strcmp(prop_name, "getcontentlanguage")) { + buffer_append_string(b,"<D:getcontentlanguage>"); + buffer_append_string(b, "en"); + buffer_append_string(b, "</D:getcontentlanguage>"); + found = 1; + } + } + + return found ? 0 : -1; +} + +static int webdav_get_property(server *srv, connection *con, plugin_data *p, physical *dst, char *prop_name, char *prop_ns, buffer *b) { + if (0 == strcmp(prop_ns, "DAV:")) { + /* a local 'live' property */ + return webdav_get_live_property(srv, con, p, dst, prop_name, b); + } else { + int found = 0; +#ifdef USE_PROPPATCH + sqlite3_stmt *stmt = p->conf.stmt_select_prop; + + if (stmt) { + /* perhaps it is in sqlite3 */ + sqlite3_reset(stmt); + + /* bind the values to the insert */ + + sqlite3_bind_text(stmt, 1, + dst->rel_path->ptr, + dst->rel_path->used - 1, + SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, + prop_name, + strlen(prop_name), + SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, + prop_ns, + strlen(prop_ns), + SQLITE_TRANSIENT); + + /* it is the PK */ + while (SQLITE_ROW == sqlite3_step(p->conf.stmt_select_prop)) { + /* there is a row for us, we only expect a single col 'value' */ + webdav_gen_prop_tag(srv, con, prop_name, prop_ns, (char *)sqlite3_column_text(p->conf.stmt_select_prop, 0), b); + found = 1; + } + } +#endif + return found ? 0 : -1; + } + + /* not found */ + return -1; +} + +typedef struct { + char *ns; + char *prop; +} webdav_property; + +webdav_property live_properties[] = { + { "DAV:", "creationdate" }, + { "DAV:", "displayname" }, + { "DAV:", "getcontentlanguage" }, + { "DAV:", "getcontentlength" }, + { "DAV:", "getcontenttype" }, + { "DAV:", "getetag" }, + { "DAV:", "getlastmodified" }, + { "DAV:", "resourcetype" }, + { "DAV:", "lockdiscovery" }, + { "DAV:", "source" }, + { "DAV:", "supportedlock" }, + + { NULL, NULL } +}; + +typedef struct { + webdav_property **ptr; + + size_t used; + size_t size; +} webdav_properties; + +static int webdav_get_props(server *srv, connection *con, plugin_data *p, physical *dst, webdav_properties *props, buffer *b_200, buffer *b_404) { + size_t i; + + if (props) { + for (i = 0; i < props->used; i++) { + webdav_property *prop; + + prop = props->ptr[i]; + + if (0 != webdav_get_property(srv, con, p, + dst, prop->prop, prop->ns, b_200)) { + webdav_gen_prop_tag(srv, con, prop->prop, prop->ns, NULL, b_404); + } + } + } else { + for (i = 0; live_properties[i].prop; i++) { + /* a local 'live' property */ + webdav_get_live_property(srv, con, p, dst, live_properties[i].prop, b_200); + } + } + + return 0; +} + +#ifdef USE_PROPPATCH +static int webdav_parse_chunkqueue(server *srv, connection *con, plugin_data *p, chunkqueue *cq, xmlDoc **ret_xml) { + xmlParserCtxtPtr ctxt; + xmlDoc *xml; + int res; + int err; + + chunk *c; + + UNUSED(con); + + /* read the chunks in to the XML document */ + ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL); + + for (c = cq->first; cq->bytes_out != cq->bytes_in; c = cq->first) { + size_t weWant = cq->bytes_out - cq->bytes_in; + size_t weHave; + + switch(c->type) { + case FILE_CHUNK: + weHave = c->file.length - c->offset; + + if (weHave > weWant) weHave = weWant; + + /* xml chunks are always memory, mmap() is our friend */ + if (c->file.mmap.start == MAP_FAILED) { + if (-1 == c->file.fd && /* open the file if not already open */ + -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } + + if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", + strerror(errno), c->file.name, c->file.fd); + + return -1; + } + + close(c->file.fd); + c->file.fd = -1; + + c->file.mmap.length = c->file.length; + + /* chunk_reset() or chunk_free() will cleanup for us */ + } + + if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->file.mmap.start + c->offset, weHave, 0))) { + log_error_write(srv, __FILE__, __LINE__, "sddd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err); + } + + c->offset += weHave; + cq->bytes_out += weHave; + + break; + case MEM_CHUNK: + /* append to the buffer */ + weHave = c->mem->used - 1 - c->offset; + + if (weHave > weWant) weHave = weWant; + + if (p->conf.log_xml) { + log_error_write(srv, __FILE__, __LINE__, "ss", "XML-request-body:", c->mem->ptr + c->offset); + } + + if (XML_ERR_OK != (err = xmlParseChunk(ctxt, c->mem->ptr + c->offset, weHave, 0))) { + log_error_write(srv, __FILE__, __LINE__, "sddd", "xmlParseChunk failed at:", cq->bytes_out, weHave, err); + } + + c->offset += weHave; + cq->bytes_out += weHave; + + break; + case UNUSED_CHUNK: + break; + } + chunkqueue_remove_finished_chunks(cq); + } + + + switch ((err = xmlParseChunk(ctxt, 0, 0, 1))) { + case XML_ERR_DOCUMENT_END: + case XML_ERR_OK: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "xmlParseChunk failed at final packet:", err); + break; + } + + xml = ctxt->myDoc; + res = ctxt->wellFormed; + xmlFreeParserCtxt(ctxt); + + if (res == 0) { + xmlFreeDoc(xml); + } else { + *ret_xml = xml; + } + + return res; +} +#endif + +URIHANDLER_FUNC(mod_webdav_subrequest_handler) { + plugin_data *p = p_d; + buffer *b; + DIR *dir; + data_string *ds; + int depth = -1; + struct stat st; + buffer *prop_200; + buffer *prop_404; + webdav_properties *req_props; + + UNUSED(srv); + + if (!p->conf.enabled) return HANDLER_GO_ON; + /* physical path is setup */ + if (con->physical.path->used == 0) return HANDLER_GO_ON; + + /* PROPFIND need them */ + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Depth"))) { + depth = strtol(ds->value->ptr, NULL, 10); + } + + switch (con->request.http_method) { + case HTTP_METHOD_PROPFIND: + /* they want to know the properties of the directory */ + req_props = NULL; + + /* is there a content-body ? */ + +#ifdef USE_PROPPATCH + /* any special requests or just allprop ? */ + if (con->request.content_length) { + xmlDocPtr xml; + + if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) { + xmlNode *rootnode = xmlDocGetRootElement(xml); + + assert(rootnode); + + if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propfind")) { + xmlNode *cmd; + + req_props = calloc(1, sizeof(*req_props)); + + for (cmd = rootnode->children; cmd; cmd = cmd->next) { + + if (0 == xmlStrcmp(cmd->name, BAD_CAST "prop")) { + /* get prop by name */ + xmlNode *prop; + + for (prop = cmd->children; prop; prop = prop->next) { + if (prop->type == XML_TEXT_NODE) continue; /* ignore WS */ + + if (prop->ns && + (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) && + (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) { + size_t i; + log_error_write(srv, __FILE__, __LINE__, "ss", + "no name space for:", + prop->name); + + xmlFreeDoc(xml); + + for (i = 0; i < req_props->used; i++) { + free(req_props->ptr[i]->ns); + free(req_props->ptr[i]->prop); + free(req_props->ptr[i]); + } + free(req_props->ptr); + free(req_props); + + con->http_status = 400; + return HANDLER_FINISHED; + } + + /* add property to requested list */ + if (req_props->size == 0) { + req_props->size = 16; + req_props->ptr = malloc(sizeof(*(req_props->ptr)) * req_props->size); + } else if (req_props->used == req_props->size) { + req_props->size += 16; + req_props->ptr = realloc(req_props->ptr, sizeof(*(req_props->ptr)) * req_props->size); + } + + req_props->ptr[req_props->used] = malloc(sizeof(webdav_property)); + req_props->ptr[req_props->used]->ns = (char *)xmlStrdup(prop->ns ? prop->ns->href : (xmlChar *)""); + req_props->ptr[req_props->used]->prop = (char *)xmlStrdup(prop->name); + req_props->used++; + } + } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "propname")) { + sqlite3_stmt *stmt = p->conf.stmt_select_propnames; + + if (stmt) { + /* get all property names (EMPTY) */ + sqlite3_reset(stmt); + /* bind the values to the insert */ + + sqlite3_bind_text(stmt, 1, + con->uri.path->ptr, + con->uri.path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + WP(); + } + } + } else if (0 == xmlStrcmp(cmd->name, BAD_CAST "allprop")) { + /* get all properties (EMPTY) */ + } + } + } + + xmlFreeDoc(xml); + } else { + con->http_status = 400; + return HANDLER_FINISHED; + } + } +#endif + con->http_status = 207; + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\"")); + + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); + + buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\">\n"); + + /* allprop */ + + prop_200 = buffer_init(); + prop_404 = buffer_init(); + + switch(depth) { + case 0: + /* Depth: 0 */ + webdav_get_props(srv, con, p, &(con->physical), req_props, prop_200, prop_404); + + buffer_append_string(b,"<D:response>\n"); + buffer_append_string(b,"<D:href>"); + buffer_append_string_buffer(b, con->uri.scheme); + buffer_append_string(b,"://"); + buffer_append_string_buffer(b, con->uri.authority); + buffer_append_string_encoded(b, CONST_BUF_LEN(con->uri.path), ENCODING_REL_URI); + buffer_append_string(b,"</D:href>\n"); + + if (!buffer_is_empty(prop_200)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_200); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + if (!buffer_is_empty(prop_404)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_404); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + + buffer_append_string(b,"</D:response>\n"); + + break; + case 1: + if (NULL != (dir = opendir(con->physical.path->ptr))) { + struct dirent *de; + physical d; + physical *dst = &(con->physical); + + d.path = buffer_init(); + d.rel_path = buffer_init(); + + while(NULL != (de = readdir(dir))) { + if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') { + continue; + /* ignore the parent dir */ + } + + buffer_copy_string_buffer(d.path, dst->path); + BUFFER_APPEND_SLASH(d.path); + + buffer_copy_string_buffer(d.rel_path, dst->rel_path); + BUFFER_APPEND_SLASH(d.rel_path); + + if (de->d_name[0] == '.' && de->d_name[1] == '\0') { + /* don't append the . */ + } else { + buffer_append_string(d.path, de->d_name); + buffer_append_string(d.rel_path, de->d_name); + } + + buffer_reset(prop_200); + buffer_reset(prop_404); + + webdav_get_props(srv, con, p, &d, req_props, prop_200, prop_404); + + buffer_append_string(b,"<D:response>\n"); + buffer_append_string(b,"<D:href>"); + buffer_append_string_buffer(b, con->uri.scheme); + buffer_append_string(b,"://"); + buffer_append_string_buffer(b, con->uri.authority); + buffer_append_string_encoded(b, CONST_BUF_LEN(d.rel_path), ENCODING_REL_URI); + buffer_append_string(b,"</D:href>\n"); + + if (!buffer_is_empty(prop_200)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_200); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 200 OK</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + if (!buffer_is_empty(prop_404)) { + buffer_append_string(b,"<D:propstat>\n"); + buffer_append_string(b,"<D:prop>\n"); + + buffer_append_string_buffer(b, prop_404); + + buffer_append_string(b,"</D:prop>\n"); + + buffer_append_string(b,"<D:status>HTTP/1.1 404 Not Found</D:status>\n"); + + buffer_append_string(b,"</D:propstat>\n"); + } + + buffer_append_string(b,"</D:response>\n"); + } + closedir(dir); + buffer_free(d.path); + buffer_free(d.rel_path); + } + break; + } + + if (req_props) { + size_t i; + for (i = 0; i < req_props->used; i++) { + free(req_props->ptr[i]->ns); + free(req_props->ptr[i]->prop); + free(req_props->ptr[i]); + } + free(req_props->ptr); + free(req_props); + } + + buffer_free(prop_200); + buffer_free(prop_404); + + buffer_append_string(b,"</D:multistatus>\n"); + + if (p->conf.log_xml) { + log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b); + } + con->file_finished = 1; + + return HANDLER_FINISHED; + case HTTP_METHOD_MKCOL: + if (p->conf.is_readonly) { + con->http_status = 403; + return HANDLER_FINISHED; + } + + if (con->request.content_length != 0) { + /* we don't support MKCOL with a body */ + con->http_status = 415; + + return HANDLER_FINISHED; + } + + /* let's create the directory */ + + if (-1 == mkdir(con->physical.path->ptr, 0700)) { + switch(errno) { + case EPERM: + con->http_status = 403; + break; + case ENOENT: + case ENOTDIR: + con->http_status = 409; + break; + case EEXIST: + default: + con->http_status = 405; /* not allowed */ + break; + } + } else { + con->http_status = 201; + } + + return HANDLER_FINISHED; + case HTTP_METHOD_DELETE: + if (p->conf.is_readonly) { + con->http_status = 403; + return HANDLER_FINISHED; + } + + /* stat and unlink afterwards */ + if (-1 == stat(con->physical.path->ptr, &st)) { + /* don't about it yet, unlink will fail too */ + switch(errno) { + case ENOENT: + con->http_status = 404; + break; + default: + con->http_status = 403; + break; + } + } else if (S_ISDIR(st.st_mode)) { + buffer *multi_status_resp = buffer_init(); + + if (webdav_delete_dir(srv, con, p, &(con->physical), multi_status_resp)) { + /* we got an error somewhere in between, build a 207 */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml; charset=\"utf-8\"")); + + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_copy_string(b, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); + + buffer_append_string(b,"<D:multistatus xmlns:D=\"DAV:\">\n"); + + buffer_append_string_buffer(b, multi_status_resp); + + buffer_append_string(b,"</D:multistatus>\n"); + + if (p->conf.log_xml) { + log_error_write(srv, __FILE__, __LINE__, "sb", "XML-response-body:", b); + } + + con->http_status = 207; + con->file_finished = 1; + } else { + /* everything went fine, remove the directory */ + + if (-1 == rmdir(con->physical.path->ptr)) { + switch(errno) { + case ENOENT: + con->http_status = 404; + break; + default: + con->http_status = 501; + break; + } + } else { + con->http_status = 204; + } + } + + buffer_free(multi_status_resp); + } else if (-1 == unlink(con->physical.path->ptr)) { + switch(errno) { + case EPERM: + con->http_status = 403; + break; + case ENOENT: + con->http_status = 404; + break; + default: + con->http_status = 501; + break; + } + } else { + con->http_status = 204; + } + return HANDLER_FINISHED; + case HTTP_METHOD_PUT: { + int fd; + chunkqueue *cq = con->request_content_queue; + + if (p->conf.is_readonly) { + con->http_status = 403; + return HANDLER_FINISHED; + } + + assert(chunkqueue_length(cq) == (off_t)con->request.content_length); + + /* taken what we have in the request-body and write it to a file */ + if (-1 == (fd = open(con->physical.path->ptr, O_WRONLY|O_CREAT|O_TRUNC, 0600))) { + /* we can't open the file */ + con->http_status = 403; + } else { + chunk *c; + + con->http_status = 201; /* created */ + + for (c = cq->first; c; c = cq->first) { + int r = 0; + + /* copy all chunks */ + switch(c->type) { + case FILE_CHUNK: + + if (c->file.mmap.start == MAP_FAILED) { + if (-1 == c->file.fd && /* open the file if not already open */ + -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } + + if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", + strerror(errno), c->file.name, c->file.fd); + + return -1; + } + + c->file.mmap.length = c->file.length; + + close(c->file.fd); + c->file.fd = -1; + + /* chunk_reset() or chunk_free() will cleanup for us */ + } + + if ((r = write(fd, c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) { + switch(errno) { + case ENOSPC: + con->http_status = 507; + + break; + default: + con->http_status = 403; + break; + } + } + break; + case MEM_CHUNK: + if ((r = write(fd, c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) { + switch(errno) { + case ENOSPC: + con->http_status = 507; + + break; + default: + con->http_status = 403; + break; + } + } + break; + case UNUSED_CHUNK: + break; + } + + if (r > 0) { + c->offset += r; + cq->bytes_out += r; + } else { + break; + } + chunkqueue_remove_finished_chunks(cq); + } + close(fd); + + } + return HANDLER_FINISHED; + } + case HTTP_METHOD_MOVE: + case HTTP_METHOD_COPY: { + buffer *destination = NULL; + char *sep, *start; + int overwrite = 1; + + if (p->conf.is_readonly) { + con->http_status = 403; + return HANDLER_FINISHED; + } + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Destination"))) { + destination = ds->value; + } else { + con->http_status = 400; + return HANDLER_FINISHED; + } + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Overwrite"))) { + if (ds->value->used != 2 || + (ds->value->ptr[0] != 'F' && + ds->value->ptr[0] != 'T') ) { + con->http_status = 400; + return HANDLER_FINISHED; + } + overwrite = (ds->value->ptr[0] == 'F' ? 0 : 1); + } + /* let's parse the Destination + * + * http://127.0.0.1:1025/dav/litmus/copydest + * + * - host has to be the same as the Host: header we got + * - we have to stay inside the document root + * - the query string is thrown away + * */ + + buffer_reset(p->uri.scheme); + buffer_reset(p->uri.path_raw); + buffer_reset(p->uri.authority); + + start = destination->ptr; + + if (NULL == (sep = strstr(start, "://"))) { + con->http_status = 400; + return HANDLER_FINISHED; + } + buffer_copy_string_len(p->uri.scheme, start, sep - start); + + start = sep + 3; + + if (NULL == (sep = strchr(start, '/'))) { + con->http_status = 400; + return HANDLER_FINISHED; + } + buffer_copy_string_len(p->uri.authority, start, sep - start); + + start = sep + 1; + + if (NULL == (sep = strchr(start, '?'))) { + /* no query string, good */ + buffer_copy_string(p->uri.path_raw, start); + } else { + buffer_copy_string_len(p->uri.path_raw, start, sep - start); + } + + if (!buffer_is_equal(p->uri.authority, con->uri.authority)) { + /* not the same host */ + con->http_status = 502; + return HANDLER_FINISHED; + } + + buffer_copy_string_buffer(p->tmp_buf, p->uri.path_raw); + buffer_urldecode_path(p->tmp_buf); + buffer_path_simplify(p->uri.path, p->tmp_buf); + + /* we now have a URI which is clean. transform it into a physical path */ + buffer_copy_string_buffer(p->physical.doc_root, con->physical.doc_root); + buffer_copy_string_buffer(p->physical.rel_path, p->uri.path); + + if (con->conf.force_lowercase_filenames) { + buffer_to_lower(p->physical.rel_path); + } + + buffer_copy_string_buffer(p->physical.path, p->physical.doc_root); + BUFFER_APPEND_SLASH(p->physical.path); + buffer_copy_string_buffer(p->physical.basedir, p->physical.path); + + /* don't add a second / */ + if (p->physical.rel_path->ptr[0] == '/') { + buffer_append_string_len(p->physical.path, p->physical.rel_path->ptr + 1, p->physical.rel_path->used - 2); + } else { + buffer_append_string_buffer(p->physical.path, p->physical.rel_path); + } + + /* let's see if the source is a directory + * if yes, we fail with 501 */ + + if (-1 == stat(con->physical.path->ptr, &st)) { + /* don't about it yet, unlink will fail too */ + switch(errno) { + case ENOENT: + con->http_status = 404; + break; + default: + con->http_status = 403; + break; + } + } else if (S_ISDIR(st.st_mode)) { + int r; + /* src is a directory */ + + if (-1 == stat(p->physical.path->ptr, &st)) { + if (-1 == mkdir(p->physical.path->ptr, 0700)) { + con->http_status = 403; + return HANDLER_FINISHED; + } + } else if (!S_ISDIR(st.st_mode)) { + if (overwrite == 0) { + /* copying into a non-dir ? */ + con->http_status = 409; + return HANDLER_FINISHED; + } else { + unlink(p->physical.path->ptr); + if (-1 == mkdir(p->physical.path->ptr, 0700)) { + con->http_status = 403; + return HANDLER_FINISHED; + } + } + } + + /* copy the content of src to dest */ + if (0 != (r = webdav_copy_dir(srv, con, p, &(con->physical), &(p->physical), overwrite))) { + con->http_status = r; + return HANDLER_FINISHED; + } + if (con->request.http_method == HTTP_METHOD_MOVE) { + b = buffer_init(); + webdav_delete_dir(srv, con, p, &(con->physical), b); /* content */ + buffer_free(b); + + rmdir(con->physical.path->ptr); + } + con->http_status = 201; + } else { + /* it is just a file, good */ + int r; + + /* destination exists */ + if (0 == (r = stat(p->physical.path->ptr, &st))) { + if (S_ISDIR(st.st_mode)) { + /* file to dir/ + * append basename to physical path */ + + if (NULL != (sep = strrchr(con->physical.path->ptr, '/'))) { + buffer_append_string(p->physical.path, sep); + r = stat(p->physical.path->ptr, &st); + } + } + } + + if (-1 == r) { + con->http_status = 201; /* we will create a new one */ + + switch(errno) { + case ENOTDIR: + con->http_status = 409; + return HANDLER_FINISHED; + } + } else if (overwrite == 0) { + /* destination exists, but overwrite is not set */ + con->http_status = 412; + return HANDLER_FINISHED; + } else { + con->http_status = 204; /* resource already existed */ + } + + if (con->request.http_method == HTTP_METHOD_MOVE) { + /* try a rename */ + + if (0 == rename(con->physical.path->ptr, p->physical.path->ptr)) { +#ifdef USE_PROPPATCH + sqlite3_stmt *stmt = p->conf.stmt_move_uri; + + if (stmt) { + + sqlite3_reset(stmt); + + /* bind the values to the insert */ + sqlite3_bind_text(stmt, 1, + p->uri.path->ptr, + p->uri.path->used - 1, + SQLITE_TRANSIENT); + + sqlite3_bind_text(stmt, 2, + con->uri.path->ptr, + con->uri.path->used - 1, + SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "sql-move failed:", sqlite3_errmsg(p->conf.sql)); + } + } +#endif + return HANDLER_FINISHED; + } + + /* rename failed, fall back to COPY + DELETE */ + } + + if (0 != (r = webdav_copy_file(srv, con, p, &(con->physical), &(p->physical), overwrite))) { + con->http_status = r; + + return HANDLER_FINISHED; + } + + if (con->request.http_method == HTTP_METHOD_MOVE) { + b = buffer_init(); + webdav_delete_file(srv, con, p, &(con->physical), b); + buffer_free(b); + } + } + + return HANDLER_FINISHED; + } + case HTTP_METHOD_PROPPATCH: { + if (p->conf.is_readonly) { + con->http_status = 403; + return HANDLER_FINISHED; + } + + /* check if destination exists */ + if (-1 == stat(con->physical.path->ptr, &st)) { + switch(errno) { + case ENOENT: + con->http_status = 404; + break; + } + } + +#ifdef USE_PROPPATCH + if (con->request.content_length) { + xmlDocPtr xml; + + if (1 == webdav_parse_chunkqueue(srv, con, p, con->request_content_queue, &xml)) { + xmlNode *rootnode = xmlDocGetRootElement(xml); + + if (0 == xmlStrcmp(rootnode->name, BAD_CAST "propertyupdate")) { + xmlNode *cmd; + char *err = NULL; + int empty_ns = 0; /* send 400 on a empty namespace attribute */ + + /* start response */ + + if (SQLITE_OK != sqlite3_exec(p->conf.sql, "BEGIN TRANSACTION", NULL, NULL, &err)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't open transaction:", err); + sqlite3_free(err); + + goto propmatch_cleanup; + } + + /* a UPDATE request, we know 'set' and 'remove' */ + for (cmd = rootnode->children; cmd; cmd = cmd->next) { + xmlNode *props; + /* either set or remove */ + + if ((0 == xmlStrcmp(cmd->name, BAD_CAST "set")) || + (0 == xmlStrcmp(cmd->name, BAD_CAST "remove"))) { + + sqlite3_stmt *stmt; + + stmt = (0 == xmlStrcmp(cmd->name, BAD_CAST "remove")) ? + p->conf.stmt_delete_prop : p->conf.stmt_update_prop; + + for (props = cmd->children; props; props = props->next) { + if (0 == xmlStrcmp(props->name, BAD_CAST "prop")) { + xmlNode *prop; + int r; + + prop = props->children; + + if (prop->ns && + (0 == xmlStrcmp(prop->ns->href, BAD_CAST "")) && + (0 != xmlStrcmp(prop->ns->prefix, BAD_CAST ""))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "no name space for:", + prop->name); + + empty_ns = 1; + break; + } + + sqlite3_reset(stmt); + + /* bind the values to the insert */ + + sqlite3_bind_text(stmt, 1, + con->uri.path->ptr, + con->uri.path->used - 1, + SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, + (char *)prop->name, + strlen((char *)prop->name), + SQLITE_TRANSIENT); + if (prop->ns) { + sqlite3_bind_text(stmt, 3, + (char *)prop->ns->href, + strlen((char *)prop->ns->href), + SQLITE_TRANSIENT); + } else { + sqlite3_bind_text(stmt, 3, + "", + 0, + SQLITE_TRANSIENT); + } + if (stmt == p->conf.stmt_update_prop) { + sqlite3_bind_text(stmt, 4, + (char *)xmlNodeGetContent(prop), + strlen((char *)xmlNodeGetContent(prop)), + SQLITE_TRANSIENT); + } + + if (SQLITE_DONE != (r = sqlite3_step(stmt))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "sql-set failed:", sqlite3_errmsg(p->conf.sql)); + } + } + } + if (empty_ns) break; + } + } + + if (empty_ns) { + if (SQLITE_OK != sqlite3_exec(p->conf.sql, "ROLLBACK", NULL, NULL, &err)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't rollback transaction:", err); + sqlite3_free(err); + + goto propmatch_cleanup; + } + + con->http_status = 400; + } else { + if (SQLITE_OK != sqlite3_exec(p->conf.sql, "COMMIT", NULL, NULL, &err)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't commit transaction:", err); + sqlite3_free(err); + + goto propmatch_cleanup; + } + con->http_status = 200; + } + con->file_finished = 1; + + return HANDLER_FINISHED; + } + +propmatch_cleanup: + xmlFreeDoc(xml); + } else { + con->http_status = 400; + return HANDLER_FINISHED; + } + } +#endif + con->http_status = 501; + return HANDLER_FINISHED; + } + default: + break; + } + + /* not found */ + return HANDLER_GO_ON; +} + + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_webdav_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("webdav"); + + p->init = mod_webdav_init; + p->handle_uri_clean = mod_webdav_uri_handler; + p->handle_physical = mod_webdav_subrequest_handler; + p->set_defaults = mod_webdav_set_defaults; + p->cleanup = mod_webdav_free; + + p->data = NULL; + + return 0; +} diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..922009f --- /dev/null +++ b/src/network.c @@ -0,0 +1,643 @@ +#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 "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; + int loops = 0; + + UNUSED(context); + + if (revents != FDEVENT_IN) { + log_error_write(srv, __FILE__, __LINE__, "sdd", + "strange event for server socket", + srv_socket->fd, + revents); + return HANDLER_ERROR; + } + + /* accept()s at most 100 connections directly + * + * we jump out after 100 to give the waiting connections a chance */ + for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) { + 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; + int is_unix_domain_socket = 0; + int fd; + +#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 (host[0] == '/') { + /* host is a unix-domain-socket */ + is_unix_domain_socket = 1; + } else if (port == 0 || port > 65535) { + log_error_write(srv, __FILE__, __LINE__, "sd", "port out of range:", port); + + return -1; + } + + if (*host == '\0') host = NULL; + + if (is_unix_domain_socket) { +#ifdef HAVE_SYS_UN_H + + srv_socket->addr.plain.sa_family = AF_UNIX; + + if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno)); + return -1; + } +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: Unix Domain sockets are not supported."); + return -1; +#endif + } + +#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; + } + + 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__, + "sds", "gethostbyname failed: ", + 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; + case AF_UNIX: + srv_socket->addr.un.sun_family = AF_UNIX; + strcpy(srv_socket->addr.un.sun_path, host); + +#ifdef SUN_LEN + addr_len = SUN_LEN(&srv_socket->addr.un); +#else + /* stevens says: */ + addr_len = strlen(host) + sizeof(srv_socket->addr.un.sun_family); +#endif + + /* check if the socket exists and try to connect to it. */ + if (-1 != (fd = connect(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len))) { + close(fd); + + log_error_write(srv, __FILE__, __LINE__, "ss", + "server socket is still in use:", + host); + + + return -1; + } + + /* connect failed */ + switch(errno) { + case ECONNREFUSED: + unlink(host); + break; + case ENOENT: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sds", + "testing socket failed:", + host, strerror(errno)); + + return -1; + } + + break; + default: + addr_len = 0; + + return -1; + } + + if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) { + switch(srv_socket->addr.plain.sa_family) { + case AF_UNIX: + log_error_write(srv, __FILE__, __LINE__, "sds", + "can't bind to socket:", + host, strerror(errno)); + break; + default: + log_error_write(srv, __FILE__, __LINE__, "ssds", + "can't bind to port:", + host, port, strerror(errno)); + break; + } + return -1; + } + + if (-1 == listen(srv_socket->fd, 128 * 8)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno)); + return -1; + } + + 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 (!buffer_is_empty(s->ssl_ca_file)) { + if (1 != SSL_CTX_load_verify_locations(s->ssl_ctx, s->ssl_ca_file->ptr, NULL)) { + log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:", + ERR_error_string(ERR_get_error(), NULL), s->ssl_ca_file); + return -1; + } + } + + if (SSL_CTX_use_certificate_file(s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM) < 0) { + log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:", + ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file (s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM) < 0) { + log_error_write(srv, __FILE__, __LINE__, "ssb", "SSL:", + ERR_error_string(ERR_get_error(), NULL), s->ssl_pemfile); + return -1; + } + + if (SSL_CTX_check_private_key(s->ssl_ctx) != 1) { + log_error_write(srv, __FILE__, __LINE__, "sssb", "SSL:", + "Private key does not match the certificate public key, reason:", + ERR_error_string(ERR_get_error(), NULL), + s->ssl_pemfile); + 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 + } else { +#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 + } + + 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 != -1) { + /* 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; +} + +typedef enum { + NETWORK_BACKEND_UNSET, + NETWORK_BACKEND_WRITE, + NETWORK_BACKEND_WRITEV, + NETWORK_BACKEND_LINUX_SENDFILE, + NETWORK_BACKEND_FREEBSD_SENDFILE, + NETWORK_BACKEND_SOLARIS_SENDFILEV +} network_backend_t; + +int network_init(server *srv) { + buffer *b; + size_t i; + network_backend_t backend; + + struct nb_map { + network_backend_t nb; + const char *name; + } network_backends[] = { + /* lowest id wins */ +#if defined USE_LINUX_SENDFILE + { NETWORK_BACKEND_LINUX_SENDFILE, "linux-sendfile" }, +#endif +#if defined USE_FREEBSD_SENDFILE + { NETWORK_BACKEND_FREEBSD_SENDFILE, "freebsd-sendfile" }, +#endif +#if defined USE_SOLARIS_SENDFILEV + { NETWORK_BACKEND_SOLARIS_SENDFILEV, "solaris-sendfilev" }, +#endif +#if defined USE_WRITEV + { NETWORK_BACKEND_WRITEV, "writev" }, +#endif + { NETWORK_BACKEND_WRITE, "write" }, + { NETWORK_BACKEND_UNSET, NULL } + }; + + 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); + +#ifdef USE_OPENSSL + srv->network_ssl_backend_write = network_write_chunkqueue_openssl; +#endif + + /* get a usefull default */ + backend = network_backends[0].nb; + + /* match name against known types */ + if (!buffer_is_empty(srv->srvconf.network_backend)) { + for (i = 0; network_backends[i].name; i++) { + /**/ + if (buffer_is_equal_string(srv->srvconf.network_backend, network_backends[i].name, strlen(network_backends[i].name))) { + backend = network_backends[i].nb; + break; + } + } + if (NULL == network_backends[i].name) { + /* we don't know it */ + + log_error_write(srv, __FILE__, __LINE__, "sb", + "server.network-backend has a unknown value:", + srv->srvconf.network_backend); + + return -1; + } + } + + switch(backend) { + case NETWORK_BACKEND_WRITE: + srv->network_backend_write = network_write_chunkqueue_write; + break; +#ifdef USE_WRITEV + case NETWORK_BACKEND_WRITEV: + srv->network_backend_write = network_write_chunkqueue_writev; + break; +#endif +#ifdef USE_LINUX_SENDFILE + case NETWORK_BACKEND_LINUX_SENDFILE: + srv->network_backend_write = network_write_chunkqueue_linuxsendfile; + break; +#endif +#ifdef USE_FREEBSD_SENDFILE + case NETWORK_BACKEND_FREEBSD_SENDFILE: + srv->network_backend_write = network_write_chunkqueue_freebsdsendfile; + break; +#endif +#ifdef USE_SOLARIS_SENDFILEV + case NETWORK_BACKEND_SOLARIS_SENDFILEV: + srv->network_backend_write = network_write_chunkqueue_solarissendfilev; + break; +#endif + default: + return -1; + } + + /* 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 (COMP_SERVER_SOCKET != dc->comp) 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->string, s)) { + return -1; + } + } + + + return 0; +} + +int network_register_fdevents(server *srv) { + size_t i; + + if (-1 == fdevent_reset(srv->ev)) { + return -1; + } + + /* 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; + off_t written = 0; +#ifdef TCP_CORK + int corked = 0; +#endif + 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 = cq->bytes_out; + +#ifdef TCP_CORK + /* Linux: put a cork into the socket as we want to combine the write() calls + * but only if we really have multiple chunks + */ + if (cq->first && cq->first->next) { + corked = 1; + setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); + } +#endif + + if (srv_socket->is_ssl) { +#ifdef USE_OPENSSL + ret = srv->network_ssl_backend_write(srv, con, con->ssl, cq); +#endif + } else { + ret = srv->network_backend_write(srv, con, con->fd, cq); + } + + if (ret >= 0) { + chunkqueue_remove_finished_chunks(cq); + ret = chunkqueue_is_empty(cq) ? 0 : 1; + } + +#ifdef TCP_CORK + if (corked) { + corked = 0; + setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); + } +#endif + + written = cq->bytes_out - 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 0000000..99c7596 --- /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 0000000..94650a7 --- /dev/null +++ b/src/network_backends.h @@ -0,0 +1,58 @@ +#ifndef _NETWORK_BACKENDS_H_ +#define _NETWORK_BACKENDS_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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, int fd, chunkqueue *cq); +int network_write_chunkqueue_writev(server *srv, connection *con, int fd, chunkqueue *cq); +int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, int fd, chunkqueue *cq); +int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, int fd, chunkqueue *cq); +int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, int fd, chunkqueue *cq); +#ifdef USE_OPENSSL +int network_write_chunkqueue_openssl(server *srv, connection *con, SSL *ssl, chunkqueue *cq); +#endif + +#endif diff --git a/src/network_freebsd_sendfile.c b/src/network_freebsd_sendfile.c new file mode 100644 index 0000000..a6994c0 --- /dev/null +++ b/src/network_freebsd_sendfile.c @@ -0,0 +1,207 @@ +#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 "stat_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, int fd, chunkqueue *cq) { + 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->mem->used == 0) { + chunks[i].iov_base = tc->mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->mem->ptr + tc->offset; + toSend = tc->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 */ + cq->bytes_out += r; + + 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; + + 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; + chunk_finished = 0; + + break; + } + } + + break; + } + case FILE_CHUNK: { + off_t offset, r; + size_t toSend; + stat_cache_entry *sce = NULL; + int ifd; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->file.name); + return -1; + } + + offset = c->file.start + c->offset; + /* limit the toSend to 2^31-1 bytes in a chunk */ + toSend = c->file.length - c->offset > ((1 << 30) - 1) ? + ((1 << 30) - 1) : c->file.length - c->offset; + + if (offset > sce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name); + + return -1; + } + + if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } + + r = 0; + + /* FreeBSD sendfile() */ + if (-1 == sendfile(ifd, fd, offset, toSend, NULL, &r, 0)) { + switch(errno) { + case EAGAIN: + break; + case ENOTCONN: + close(ifd); + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno); + close(ifd); + return -1; + } + } + close(ifd); + + c->offset += r; + cq->bytes_out += r; + + if (c->offset == c->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 0000000..6426568 --- /dev/null +++ b/src/network_linux_sendfile.c @@ -0,0 +1,245 @@ +#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 <fcntl.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "stat_cache.h" + +int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, int fd, chunkqueue *cq) { + 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->mem->used == 0) { + chunks[i].iov_base = tc->mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->mem->ptr + tc->offset; + toSend = tc->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 */ + cq->bytes_out += r; + + 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; + + 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; + chunk_finished = 0; + + break; + } + } + + break; + } + case FILE_CHUNK: { + ssize_t r; + off_t offset; + size_t toSend; + stat_cache_entry *sce = NULL; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->file.name); + return -1; + } + + offset = c->file.start + c->offset; + /* limit the toSend to 2^31-1 bytes in a chunk */ + toSend = c->file.length - c->offset > ((1 << 30) - 1) ? + ((1 << 30) - 1) : c->file.length - c->offset; + + if (offset > sce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name); + + return -1; + } + + /* open file if not already opened */ + if (-1 == c->file.fd) { + if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } +#ifdef FD_CLOEXEC + fcntl(c->file.fd, F_SETFD, FD_CLOEXEC); +#endif +#ifdef HAVE_POSIX_FADVISE + /* tell the kernel that we want to stream the file */ + if (-1 == posix_fadvise(c->file.fd, 0, 0, POSIX_FADV_SEQUENTIAL)) { + log_error_write(srv, __FILE__, __LINE__, "ssd", + "posix_fadvise failed:", strerror(errno), c->file.fd); + } +#endif + } + + + /* Linux sendfile() */ + if (-1 == (r = sendfile(fd, c->file.fd, &offset, toSend))) { + switch (errno) { + case EAGAIN: + case EINTR: + r = 0; + break; + case EPIPE: + case ECONNRESET: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "sendfile failed:", strerror(errno), fd); + return -1; + } + } + + if (r == 0) { + /* we got a event to write put we couldn't. remote side closed ? */ + return -2; + } + +#ifdef HAVE_POSIX_FADVISE +#if 0 +#define K * 1024 +#define M * 1024 K +#define READ_AHEAD 4 M + /* check if we need a new chunk */ + if ((c->offset & ~(READ_AHEAD - 1)) != ((c->offset + r) & ~(READ_AHEAD - 1))) { + /* tell the kernel that we want to stream the file */ + if (-1 == posix_fadvise(c->file.fd, (c->offset + r) & ~(READ_AHEAD - 1), READ_AHEAD, POSIX_FADV_NOREUSE)) { + log_error_write(srv, __FILE__, __LINE__, "ssd", + "posix_fadvise failed:", strerror(errno), c->file.fd); + } + } +#endif +#endif + + c->offset += r; + cq->bytes_out += r; + + if (c->offset == c->file.length) { + chunk_finished = 1; + + /* chunk_free() / chunk_reset() will cleanup for us but it is a ok to be faster :) */ + + if (c->file.fd != -1) { + close(c->file.fd); + c->file.fd = -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 0000000..b6a1b2f --- /dev/null +++ b/src/network_openssl.c @@ -0,0 +1,273 @@ +#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 <assert.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "stat_cache.h" + +# include <openssl/ssl.h> +# include <openssl/err.h> + +int network_write_chunkqueue_openssl(server *srv, connection *con, SSL *ssl, chunkqueue *cq) { + int ssl_r; + chunk *c; + size_t chunks_written = 0; + + /* this is a 64k sendbuffer + * + * it has to stay at the same location all the time to satisfy the needs + * of SSL_write to pass the SAME parameter in case of a _WANT_WRITE + * + * the buffer is allocated once, is NOT realloced and is NOT freed at shutdown + * -> we expect a 64k block to 'leak' in valgrind + * + * + * In reality we would like to use mmap() but we don't have a guarantee that + * we get the same mmap() address for each call. On openbsd the mmap() address + * even randomized. + * That means either we keep the mmap() open or we do a read() into a + * constant buffer + * */ +#define LOCAL_SEND_BUFSIZE (64 * 1024) + static char *local_send_buffer = NULL; + + /* the remote side closed the connection before without shutdown request + * - IE + * - wget + * if keep-alive is disabled */ + + if (con->keep_alive == 0) { + SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); + } + + 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->mem->used == 0) { + chunk_finished = 1; + break; + } + + offset = c->mem->ptr + c->offset; + toSend = c->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(ssl, offset, toSend)) <= 0) { + unsigned long err; + + switch ((ssl_r = SSL_get_error(ssl, r))) { + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SYSCALL: + /* perhaps we have error waiting in our error-queue */ + if (0 != (err = ERR_get_error())) { + do { + log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", + ssl_r, r, + ERR_error_string(err, NULL)); + } while((err = ERR_get_error())); + } else if (r == -1) { + /* no, but we have errno */ + switch(errno) { + case EPIPE: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:", + ssl_r, r, errno, + strerror(errno)); + break; + } + } else { + /* neither error-queue nor errno ? */ + log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL (error):", + ssl_r, r, errno, + strerror(errno)); + } + + return -1; + case SSL_ERROR_ZERO_RETURN: + /* clean shutdown on the remote side */ + + if (r == 0) return -2; + + /* fall through */ + default: + while((err = ERR_get_error())) { + log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", + ssl_r, r, + ERR_error_string(err, NULL)); + } + + return -1; + } + } else { + c->offset += r; + cq->bytes_out += r; + } + + if (c->offset == (off_t)c->mem->used - 1) { + chunk_finished = 1; + } + + break; + } + case FILE_CHUNK: { + char *s; + ssize_t r; + stat_cache_entry *sce = NULL; + int ifd; + int write_wait = 0; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->file.name); + return -1; + } + + if (NULL == local_send_buffer) { + local_send_buffer = malloc(LOCAL_SEND_BUFSIZE); + assert(local_send_buffer); + } + + do { + off_t offset = c->file.start + c->offset; + off_t toSend = c->file.length - c->offset; + + if (toSend > LOCAL_SEND_BUFSIZE) toSend = LOCAL_SEND_BUFSIZE; + + if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed:", strerror(errno)); + + return -1; + } + + + lseek(ifd, offset, SEEK_SET); + if (-1 == (toSend = read(ifd, local_send_buffer, toSend))) { + close(ifd); + log_error_write(srv, __FILE__, __LINE__, "ss", "read failed:", strerror(errno)); + return -1; + } + + s = local_send_buffer; + + close(ifd); + + if ((r = SSL_write(ssl, s, toSend)) <= 0) { + unsigned long err; + + switch ((ssl_r = SSL_get_error(ssl, r))) { + case SSL_ERROR_WANT_WRITE: + write_wait = 1; + break; + case SSL_ERROR_SYSCALL: + /* perhaps we have error waiting in our error-queue */ + if (0 != (err = ERR_get_error())) { + do { + log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", + ssl_r, r, + ERR_error_string(err, NULL)); + } while((err = ERR_get_error())); + } else if (r == -1) { + /* no, but we have errno */ + switch(errno) { + case EPIPE: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:", + ssl_r, r, errno, + strerror(errno)); + break; + } + } else { + /* neither error-queue nor errno ? */ + log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL (error):", + ssl_r, r, errno, + strerror(errno)); + } + + return -1; + case SSL_ERROR_ZERO_RETURN: + /* clean shutdown on the remote side */ + + if (r == 0) return -2; + + /* fall thourgh */ + default: + while((err = ERR_get_error())) { + log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", + ssl_r, r, + ERR_error_string(err, NULL)); + } + + return -1; + } + } else { + c->offset += r; + cq->bytes_out += r; + } + + if (c->offset == c->file.length) { + chunk_finished = 1; + } + } while(!chunk_finished && !write_wait); + + 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 0000000..0ab669f --- /dev/null +++ b/src/network_solaris_sendfilev.c @@ -0,0 +1,213 @@ +#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 "stat_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, int fd, chunkqueue *cq) { + 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->mem->used == 0) { + chunks[i].iov_base = tc->mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->mem->ptr + tc->offset; + toSend = tc->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 */ + cq->bytes_out += r; + + 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; + + 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; + chunk_finished = 0; + + break; + } + } + + break; + } + case FILE_CHUNK: { + ssize_t r; + off_t offset; + size_t toSend, written; + sendfilevec_t fvec; + stat_cache_entry *sce = NULL; + int ifd; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->file.name); + return -1; + } + + offset = c->file.start + c->offset; + toSend = c->file.length - c->offset; + + if (offset > sce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name); + + return -1; + } + + if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } + + fvec.sfv_fd = ifd; + 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); + + close(ifd); + return -1; + } + + r = 0; + } + + close(ifd); + c->offset += written; + cq->bytes_out += written; + + if (c->offset == c->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 0000000..90fc2ac --- /dev/null +++ b/src/network_write.c @@ -0,0 +1,168 @@ +#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 "network.h" +#include "fdevent.h" +#include "log.h" +#include "stat_cache.h" + +#include "sys-socket.h" + +#include "network_backends.h" + +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +int network_write_chunkqueue_write(server *srv, connection *con, int fd, chunkqueue *cq) { + 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->mem->used == 0) { + chunk_finished = 1; + break; + } + + offset = c->mem->ptr + c->offset; + toSend = c->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; + cq->bytes_out += r; + + if (c->offset == (off_t)c->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; + stat_cache_entry *sce = NULL; + int ifd; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->file.name); + return -1; + } + + offset = c->file.start + c->offset; + toSend = c->file.length - c->offset; + + if (offset > sce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->file.name); + + return -1; + } + + if (-1 == (ifd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + return -1; + } + +#if defined USE_MMAP + if (MAP_FAILED == (p = mmap(0, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno)); + + close(ifd); + + return -1; + } + close(ifd); + + if ((r = write(fd, p + offset, toSend)) <= 0) { + log_error_write(srv, __FILE__, __LINE__, "ss", "write failed: ", strerror(errno)); + munmap(p, sce->st.st_size); + return -1; + } + + munmap(p, sce->st.st_size); +#else + buffer_prepare_copy(srv->tmp_buf, toSend); + + lseek(ifd, offset, SEEK_SET); + if (-1 == (toSend = read(ifd, srv->tmp_buf->ptr, toSend))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno)); + close(ifd); + + return -1; + } + close(ifd); + + if (-1 == (r = send(fd, srv->tmp_buf->ptr, toSend, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno)); + + return -1; + } +#endif + c->offset += r; + cq->bytes_out += r; + + if (c->offset == c->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 0000000..578048e --- /dev/null +++ b/src/network_writev.c @@ -0,0 +1,343 @@ +#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 <assert.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "stat_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 + +#if 0 +#define LOCAL_BUFFERING 1 +#endif + +int network_write_chunkqueue_writev(server *srv, connection *con, int fd, chunkqueue *cq) { + 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->mem->used == 0) { + chunks[i].iov_base = tc->mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->mem->ptr + tc->offset; + toSend = tc->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; + } + } + + cq->bytes_out += r; + + /* 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; + + 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; + chunk_finished = 0; + + break; + } + } + + break; + } + case FILE_CHUNK: { + ssize_t r; + off_t abs_offset; + off_t toSend; + stat_cache_entry *sce = NULL; + +#define KByte * 1024 +#define MByte * 1024 KByte +#define GByte * 1024 MByte + const off_t we_want_to_mmap = 512 KByte; + char *start = NULL; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->file.name); + return -1; + } + + abs_offset = c->file.start + c->offset; + + if (abs_offset > sce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "file was shrinked:", c->file.name); + + return -1; + } + + /* mmap the buffer + * - first mmap + * - new mmap as the we are at the end of the last one */ + if (c->file.mmap.start == MAP_FAILED || + abs_offset == (off_t)(c->file.mmap.offset + c->file.mmap.length)) { + + /* Optimizations for the future: + * + * adaptive mem-mapping + * the problem: + * we mmap() the whole file. If someone has alot large files and 32bit + * machine the virtual address area will be unrun and we will have a failing + * mmap() call. + * solution: + * only mmap 16M in one chunk and move the window as soon as we have finished + * the first 8M + * + * read-ahead buffering + * the problem: + * sending out several large files in parallel trashes the read-ahead of the + * kernel leading to long wait-for-seek times. + * solutions: (increasing complexity) + * 1. use madvise + * 2. use a internal read-ahead buffer in the chunk-structure + * 3. use non-blocking IO for file-transfers + * */ + + /* all mmap()ed areas are 512kb expect the last which might be smaller */ + off_t we_want_to_send; + size_t to_mmap; + + /* this is a remap, move the mmap-offset */ + if (c->file.mmap.start != MAP_FAILED) { + munmap(c->file.mmap.start, c->file.mmap.length); + c->file.mmap.offset += we_want_to_mmap; + } else { + /* in case the range-offset is after the first mmap()ed area we skip the area */ + c->file.mmap.offset = 0; + + while (c->file.mmap.offset + we_want_to_mmap < c->file.start) { + c->file.mmap.offset += we_want_to_mmap; + } + } + + /* length is rel, c->offset too, assume there is no limit at the mmap-boundaries */ + we_want_to_send = c->file.length - c->offset; + to_mmap = (c->file.start + c->file.length) - c->file.mmap.offset; + + /* we have more to send than we can mmap() at once */ + if (abs_offset + we_want_to_send > c->file.mmap.offset + we_want_to_mmap) { + we_want_to_send = (c->file.mmap.offset + we_want_to_mmap) - abs_offset; + to_mmap = we_want_to_mmap; + } + + if (-1 == c->file.fd) { /* open the file if not already open */ + if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", "open failed for:", c->file.name, strerror(errno)); + + return -1; + } +#ifdef FD_CLOEXEC + fcntl(c->file.fd, F_SETFD, FD_CLOEXEC); +#endif + } + + if (MAP_FAILED == (c->file.mmap.start = mmap(0, to_mmap, PROT_READ, MAP_SHARED, c->file.fd, c->file.mmap.offset))) { + /* close it here, otherwise we'd have to set FD_CLOEXEC */ + + log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed:", + strerror(errno), c->file.name, c->file.fd); + + return -1; + } + + c->file.mmap.length = to_mmap; +#ifdef LOCAL_BUFFERING + buffer_copy_string_len(c->mem, c->file.mmap.start, c->file.mmap.length); +#else +#ifdef HAVE_POSIX_MADVISE + /* don't advise files < 64Kb */ + if (c->file.mmap.length > (64 KByte)) { + /* darwin 7 is returning EINVAL all the time and I don't know how to + * detect this at runtime.i + * + * ignore the return value for now */ + posix_madvise(c->file.mmap.start, c->file.mmap.length, POSIX_MADV_WILLNEED); + } +#endif +#endif + + /* chunk_reset() or chunk_free() will cleanup for us */ + } + + /* to_send = abs_mmap_end - abs_offset */ + toSend = (c->file.mmap.offset + c->file.mmap.length) - (abs_offset); + + if (toSend < 0) { + log_error_write(srv, __FILE__, __LINE__, "soooo", + "toSend is negative:", + toSend, + c->file.mmap.length, + abs_offset, + c->file.mmap.offset); + assert(toSend < 0); + } + +#ifdef LOCAL_BUFFERING + start = c->mem->ptr; +#else + start = c->file.mmap.start; +#endif + + if ((r = write(fd, start + (abs_offset - c->file.mmap.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; + } + } + + c->offset += r; + cq->bytes_out += r; + + if (c->offset == c->file.length) { + chunk_finished = 1; + + /* we don't need the mmaping anymore */ + if (c->file.mmap.start != MAP_FAILED) { + munmap(c->file.mmap.start, c->file.mmap.length); + c->file.mmap.start = MAP_FAILED; + } + } + + 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 0000000..e74d8b0 --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,451 @@ +#include <string.h> +#include <stdlib.h> + +#include <stdio.h> + +#include "plugin.h" +#include "log.h" +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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_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_HANDLE_PHYSICAL, + 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 + +#ifndef LIGHTTPD_STATIC + if (use_dlclose && p->lib) { +#ifdef __WIN32 + FreeLibrary(p->lib); +#else + dlclose(p->lib); +#endif + } +#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; +} + +/** + * + * + * + */ + +#ifdef LIGHTTPD_STATIC +int plugins_load(server *srv) { + plugin *p; +#define PLUGIN_INIT(x)\ + p = plugin_init(); \ + if (x ## _plugin_init(p)) { \ + log_error_write(srv, __FILE__, __LINE__, "ss", #x, "plugin init failed" ); \ + plugin_free(p); \ + return -1;\ + }\ + plugins_register(srv, p); + +#include "plugin-static.h" + + return 0; +} +#else +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; + + buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_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__, "sbs", "dlopen() failed for:", + srv->tmp_buf, 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 +#if 1 + init = (int (*)(plugin *))dlsym(p->lib, srv->tmp_buf->ptr); +#else + *(void **)(&init) = dlsym(p->lib, srv->tmp_buf->ptr); +#endif + 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; +} +#endif + +#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_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_HANDLE_PHYSICAL, handle_physical) +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_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_HANDLE_PHYSICAL, handle_physical); + 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; + srv->plugins.used = 0; +} diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 0000000..b43129a --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,92 @@ +#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) (server *srv, connection *con, void *p_d); /* mapping url to physical path */ + 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_physical(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_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, comp_key_t comp); +int config_check_cond(server *srv, connection *con, data_config *dc); +int config_append_cond_match_buffer(connection *con, data_config *dc, buffer *buf, int n); + +#endif diff --git a/src/proc_open.c b/src/proc_open.c new file mode 100644 index 0000000..ecea815 --- /dev/null +++ b/src/proc_open.c @@ -0,0 +1,386 @@ +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include "proc_open.h" + +#ifdef WIN32 +#include <io.h> +#include <fcntl.h> +#else +#include <sys/wait.h> +#include <unistd.h> +#endif + + +#ifdef WIN32 +/* {{{ win32 stuff */ +# define SHELLENV "ComSpec" +# define SECURITY_DC , SECURITY_ATTRIBUTES *security +# define SECURITY_CC , security +# define pipe(pair) (CreatePipe(&pair[0], &pair[1], security, 2048L) ? 0 : -1) +static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig) +{ + HANDLE copy, self = GetCurrentProcess(); + + if (!DuplicateHandle(self, src, self, ©, 0, inherit, DUPLICATE_SAME_ACCESS | + (closeorig ? DUPLICATE_CLOSE_SOURCE : 0))) + return NULL; + return copy; +} +# define close_descriptor(fd) CloseHandle(fd) +static void pipe_close_parent(pipe_t *p) { + /* don't let the child inherit the parent side of the pipe */ + p->parent = dup_handle(p->parent, FALSE, TRUE); +} +static void pipe_close_child(pipe_t *p) { + close_descriptor(p->child); + p->fd = _open_osfhandle((long)p->parent, + (p->fd == 0 ? O_RDONLY : O_WRONLY)|O_BINARY); +} +/* }}} */ +#else /* WIN32 */ +/* {{{ unix way */ +# define SHELLENV "SHELL" +# define SECURITY_DC +# define SECURITY_CC +# define close_descriptor(fd) close(fd) +static void pipe_close_parent(pipe_t *p) { + /* don't close stdin */ + close_descriptor(p->parent); + if (dup2(p->child, p->fd) != p->fd) { + perror("pipe_child dup2"); + } else { + close_descriptor(p->child); + p->child = p->fd; + } +} +static void pipe_close_child(pipe_t *p) { + close_descriptor(p->child); + p->fd = p->parent; +} +/* }}} */ +#endif /* WIN32 */ + +/* {{{ pipe_close */ +static void pipe_close(pipe_t *p) { + close_descriptor(p->parent); + close_descriptor(p->child); +#ifdef WIN32 + close(p->fd); +#endif +} +/* }}} */ +/* {{{ pipe_open */ +static int pipe_open(pipe_t *p, int fd SECURITY_DC) { + descriptor_t newpipe[2]; + + if (0 != pipe(newpipe)) { + fprintf(stderr, "can't open pipe"); + return -1; + } + if (0 == fd) { + p->parent = newpipe[1]; /* write */ + p->child = newpipe[0]; /* read */ + } else { + p->parent = newpipe[0]; /* read */ + p->child = newpipe[1]; /* write */ + } + p->fd = fd; + + return 0; +} +/* }}} */ + +/* {{{ proc_open_pipes */ +static int proc_open_pipes(proc_handler_t *proc SECURITY_DC) { + if (pipe_open(&(proc->in), 0 SECURITY_CC) != 0) { + return -1; + } + if (pipe_open(&(proc->out), 1 SECURITY_CC) != 0) { + return -1; + } + if (pipe_open(&(proc->err), 2 SECURITY_CC) != 0) { + return -1; + } + return 0; +} +/* }}} */ +/* {{{ proc_close_pipes */ +static void proc_close_pipes(proc_handler_t *proc) { + pipe_close(&proc->in); + pipe_close(&proc->out); + pipe_close(&proc->err); +} +/* }}} */ +/* {{{ proc_close_parents */ +static void proc_close_parents(proc_handler_t *proc) { + pipe_close_parent(&proc->in); + pipe_close_parent(&proc->out); + pipe_close_parent(&proc->err); +} +/* }}} */ +/* {{{ proc_close_childs */ +static void proc_close_childs(proc_handler_t *proc) { + pipe_close_child(&proc->in); + pipe_close_child(&proc->out); + pipe_close_child(&proc->err); +} +/* }}} */ + +#ifdef WIN32 +/* {{{ proc_close */ +int proc_close(proc_handler_t *proc) { + proc_pid_t child = proc->child; + DWORD wstatus; + + proc_close_pipes(proc); + WaitForSingleObject(child, INFINITE); + GetExitCodeProcess(child, &wstatus); + CloseHandle(child); + + return wstatus; +} +/* }}} */ +/* {{{ proc_open */ +int proc_open(proc_handler_t *proc, const char *command) { + PROCESS_INFORMATION pi; + STARTUPINFO si; + BOOL procok; + SECURITY_ATTRIBUTES security; + const char *shell; + buffer *cmdline; + + if (NULL == (shell = getenv(SHELLENV))) { + fprintf(stderr, "env %s is required", SHELLENV); + return -1; + } + + /* we use this to allow the child to inherit handles */ + memset(&security, 0, sizeof(security)); + security.nLength = sizeof(security); + security.bInheritHandle = TRUE; + security.lpSecurityDescriptor = NULL; + + if (proc_open_pipes(proc, &security) != 0) { + return -1; + } + proc_close_parents(proc); + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = proc->in.child; + si.hStdOutput = proc->out.child; + si.hStdError = proc->err.child; + + memset(&pi, 0, sizeof(pi)); + + cmdline = buffer_init(); + buffer_append_string(cmdline, shell); + buffer_append_string_len(cmdline, CONST_STR_LEN(" /c ")); + buffer_append_string(cmdline, command); + procok = CreateProcess(NULL, cmdline->ptr, &security, &security, TRUE, + NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); + buffer_free(cmdline); + + if (FALSE == procok) { + fprintf(stderr, "failed to CreateProcess"); + return -1; + } + + proc->child = pi.hProcess; + CloseHandle(pi.hThread); + + proc_close_childs(proc); + + return 0; +} +/* }}} */ +#else /* WIN32 */ +/* {{{ proc_close */ +int proc_close(proc_handler_t *proc) { + pid_t child = proc->child; + int wstatus; + pid_t wait_pid; + + proc_close_pipes(proc); + + do { + wait_pid = waitpid(child, &wstatus, 0); + } while (wait_pid == -1 && errno == EINTR); + + if (wait_pid == -1) { + return -1; + } else { + if (WIFEXITED(wstatus)) + wstatus = WEXITSTATUS(wstatus); + } + + return wstatus; +} +/* }}} */ +/* {{{ proc_open */ +int proc_open(proc_handler_t *proc, const char *command) { + pid_t child; + const char *shell; + + if (NULL == (shell = getenv(SHELLENV))) { + fprintf(stderr, "env %s is required", SHELLENV); + return -1; + } + + if (proc_open_pipes(proc) != 0) { + return -1; + } + + /* the unix way */ + + child = fork(); + + if (child == 0) { + /* this is the child process */ + + /* close those descriptors that we just opened for the parent stuff, + * dup new descriptors into required descriptors and close the original + * cruft + */ + proc_close_parents(proc); + + execl(shell, shell, "-c", command, NULL); + _exit(127); + + } else if (child < 0) { + fprintf(stderr, "failed to forking"); + proc_close(proc); + return -1; + + } else { + proc->child = child; + proc_close_childs(proc); + return 0; + } +} +/* }}} */ +#endif /* WIN32 */ + +/* {{{ proc_read_fd_to_buffer */ +static void proc_read_fd_to_buffer(int fd, buffer *b) { + ssize_t s; + + for (;;) { + buffer_prepare_append(b, 512); + if ((s = read(fd, (void *)(b->ptr + b->used), 512 - 1)) <= 0) { + break; + } + b->used += s; + } + b->ptr[b->used] = '\0'; +} +/* }}} */ +/* {{{ proc_open_buffer */ +int proc_open_buffer(proc_handler_t *proc, const char *command, buffer *in, buffer *out, buffer *err) { + + UNUSED(err); + + if (proc_open(proc, command) != 0) { + return -1; + } + + if (in) { + if (write(proc->in.fd, (void *)in->ptr, in->used) < 0) { + perror("error writing pipe"); + return -1; + } + } + pipe_close(&proc->in); + + if (out) { + proc_read_fd_to_buffer(proc->out.fd, out); + } + pipe_close(&proc->out); + + if (err) { + proc_read_fd_to_buffer(proc->err.fd, err); + } + pipe_close(&proc->err); + + return 0; +} +/* }}} */ + +/* {{{ test */ +#ifdef DEBUG_PROC_OPEN +int main() { + proc_handler_t proc; + buffer *in = buffer_init(), *out = buffer_init(), *err = buffer_init(); + int wstatus; + +#define FREE() do { \ + buffer_free(in); \ + buffer_free(out); \ + buffer_free(err); \ +} while (0) + +#define RESET() do { \ + buffer_reset(in); \ + buffer_reset(out); \ + buffer_reset(err); \ + wstatus = proc_close(&proc); \ + if (0&&wstatus != 0) { \ + fprintf(stdout, "exitstatus %d\n", wstatus); \ + return __LINE__ - 200; \ + } \ +} while (0) + +#define ERROR_OUT() do { \ + fprintf(stdout, "failed opening proc\n"); \ + wstatus = proc_close(&proc); \ + fprintf(stdout, "exitstatus %d\n", wstatus); \ + FREE(); \ + return __LINE__ - 300; \ +} while (0) + +#ifdef WIN32 +#define CMD_CAT "pause" +#else +#define CMD_CAT "cat" +#endif + + do { + fprintf(stdout, "test: echo 123 without read\n"); + if (proc_open(&proc, "echo 321") != 0) { + ERROR_OUT(); + } + close_descriptor(proc.in.parent); + close_descriptor(proc.out.parent); + close_descriptor(proc.err.parent); + RESET(); + + fprintf(stdout, "test: echo 321 with read\n"); fflush(stdout); + if (proc_open_buffer(&proc, "echo 321", NULL, out, err) != 0) { + ERROR_OUT(); + } + fprintf(stdout, "result: ->%s<-\n\n", out->ptr); fflush(stdout); + RESET(); + + fprintf(stdout, "test: echo 123 | " CMD_CAT "\n"); fflush(stdout); + buffer_copy_string_len(in, CONST_STR_LEN("123\n")); + if (proc_open_buffer(&proc, CMD_CAT, in, out, err) != 0) { + ERROR_OUT(); + } + fprintf(stdout, "result: ->%s<-\n\n", out->ptr); fflush(stdout); + RESET(); + } while (0); + +#undef RESET +#undef ERROR_OUT + + fprintf(stdout, "ok\n"); + + FREE(); + return 0; +} +#endif /* DEBUG_PROC_OPEN */ +/* }}} */ + diff --git a/src/proc_open.h b/src/proc_open.h new file mode 100644 index 0000000..e07421a --- /dev/null +++ b/src/proc_open.h @@ -0,0 +1,25 @@ + +#include "buffer.h" + +#ifdef WIN32 +#include <windows.h> +typedef HANDLE descriptor_t; +typedef HANDLE proc_pid_t; +#else +typedef int descriptor_t; +typedef pid_t proc_pid_t; +#endif + +typedef struct { + descriptor_t parent, child; + int fd; +} pipe_t; + +typedef struct { + pipe_t in, out, err; + proc_pid_t child; +} proc_handler_t; + +int proc_close(proc_handler_t *ht); +int proc_open(proc_handler_t *ht, const char *command); +int proc_open_buffer(proc_handler_t *ht, const char *command, buffer *in, buffer *out, buffer *err); diff --git a/src/request.c b/src/request.c new file mode 100644 index 0000000..020333e --- /dev/null +++ b/src/request.c @@ -0,0 +1,1083 @@ +#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--) { + const 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(unsigned char c) { + if (c <= 32) return 0; + if (c == 127) return 0; + if (c == 255) return 0; + + return 1; +} + +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; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400"); + 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; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501"); + 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; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown HTTP version -> 505"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } else { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400"); + 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])) { + unsigned char buf[2]; + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + buf[0] = con->request.uri->ptr[j]; + buf[1] = '\0'; + + if (con->request.uri->ptr[j] > 32 && + con->request.uri->ptr[j] != 127) { + /* the character is printable -> print it */ + log_error_write(srv, __FILE__, __LINE__, "ss", + "invalid character in URI -> 400", + buf); + } else { + /* a control-character, print ascii-code */ + log_error_write(srv, __FILE__, __LINE__, "sd", + "invalid character in URI -> 400", + con->request.uri->ptr[j]); + } + + 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; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + + request_line_stage++; + break; + } + } + + in_folding = 0; + + if (con->request.uri->used == 1) { + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "s", "no uri specified -> 400"); + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 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 */ + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "WS character in key -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + 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 { + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "CR without LF -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + 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; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "sbsds", + "CTL character in key", con->request.request, cur, *cur, "-> 400"); + + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + 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 */ + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "WS at the start of first line -> 400"); + + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + + 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 (con_length_set) { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "duplicate Content-Length-header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + + 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")))) { + /* if dup, only the first one will survive */ + if (!con->request.http_content_type) { + con->request.http_content_type = ds->value->ptr; + } else { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "duplicate Content-Type-header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } 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) + * + * What has to be added ? + * 1. handling of chunked request body + * 2. out-of-order sending from the HTTP/1.1 100 Continue + * header + * + */ + + 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")))) { + if (!con->request.http_host) { + con->request.http_host = ds->value; + } else { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "duplicate Host-header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) { + /* Proxies sometimes send dup headers + * if they are the same we ignore the second + * if not, we raise an error */ + if (!con->request.http_if_modified_since) { + con->request.http_if_modified_since = ds->value->ptr; + } else if (0 == strcasecmp(con->request.http_if_modified_since, + ds->value->ptr)) { + /* ignore it if they are the same */ + } else { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "duplicate If-Modified-Since header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) { + /* if dup, only the first one will survive */ + if (!con->request.http_if_none_match) { + con->request.http_if_none_match = ds->value->ptr; + } else { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "duplicate If-None-Match-header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Range")))) { + if (!con->request.http_range) { + /* bytes=.*-.* */ + + if (0 == strncasecmp(ds->value->ptr, "bytes=", 6) && + NULL != strchr(ds->value->ptr+6, '-')) { + + /* if dup, only the first one will survive */ + con->request.http_range = ds->value->ptr + 6; + } + } else { + con->http_status = 400; + con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "duplicate Range-header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } + + 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 { + if (srv->srvconf.log_request_header_on_error) { + 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; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + 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 it is set */ + if (NULL != con->request.http_host && + 0 != request_check_hostname(srv, con, con->request.http_host)) { + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", + "Invalid Hostname -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + return 0; + } + + switch(con->request.http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + case HTTP_METHOD_OPTIONS: + /* content-length is forbidden for those */ + if (con_length_set && con->request.content_length != 0) { + /* content-length is missing */ + log_error_write(srv, __FILE__, __LINE__, "s", + "GET/HEAD/OPTIONS with content-length -> 400"); + + con->keep_alive = 0; + con->http_status = 400; + return 0; + } + break; + case HTTP_METHOD_POST: + /* content-length is required for them */ + if (!con_length_set) { + /* content-length is missing */ + log_error_write(srv, __FILE__, __LINE__, "s", + "POST-request, but content-length missing -> 411"); + + con->keep_alive = 0; + con->http_status = 411; + return 0; + + } + break; + default: + /* the may have a content-length */ + break; + } + + + /* check if we have read post data */ + if (con_length_set) { + /* don't handle more the SSIZE_MAX bytes in content-length */ + if (con->request.content_length > SSIZE_MAX) { + con->http_status = 413; + con->keep_alive = 0; + + 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->srvconf.max_request_size != 0 && + (con->request.content_length >> 10) > srv->srvconf.max_request_size) { + /* the request body itself is larger then + * our our max_request_size + */ + + con->http_status = 413; + con->keep_alive = 0; + + 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 0000000..cf2b07d --- /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 0000000..d955cec --- /dev/null +++ b/src/response.c @@ -0,0 +1,591 @@ +#include <sys/types.h> +#include <sys/stat.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 "stat_cache.h" +#include "chunk.h" + +#include "connections.h" + +#include "plugin.h" + +#include "sys-socket.h" + +int http_response_write_header(server *srv, connection *con) { + buffer *b; + size_t i; + int have_date = 0; + int have_server = 0; + + 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"); + } + + + /* 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)) { + if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Date"))) have_date = 1; + if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Server"))) have_server = 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 (!have_date) { + /* 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 (!have_server) { + if (buffer_is_empty(con->conf.server_tag)) { + BUFFER_APPEND_STRING_CONST(b, "\r\nServer: " PACKAGE_NAME "/" 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; +} + + + +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; + + /* we only come here when we have the parse the full request again + * + * a HANDLER_COMEBACK from mod_rewrite and mod_fastcgi might be a + * problem here as mod_setenv might get called multiple times + * + * fastcgi-auth might lead to a COMEBACK too + * fastcgi again dead server too + * + * mod_compress might add headers twice too + * + * */ + + if (con->conf.log_condition_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "run condition"); + } + config_patch_connection(srv, con, COMP_SERVER_SOCKET); /* 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 ? "https" : "http"); + buffer_copy_string_buffer(con->uri.authority, con->request.http_host); + buffer_to_lower(con->uri.authority); + + config_patch_connection(srv, con, COMP_HTTP_HOST); /* Host: */ + config_patch_connection(srv, con, COMP_HTTP_REMOTEIP); /* Client-IP */ + config_patch_connection(srv, con, COMP_HTTP_REFERER); /* Referer: */ + config_patch_connection(srv, con, COMP_HTTP_USERAGENT); /* User-Agent: */ + config_patch_connection(srv, con, COMP_HTTP_COOKIE); /* 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. /../) + */ + + + + if (con->request.http_method == HTTP_METHOD_OPTIONS && + con->uri.path_raw->ptr[0] == '*' && con->uri.path_raw->ptr[1] == '\0') { + /* OPTIONS * ... */ + buffer_copy_string_buffer(con->uri.path, con->uri.path_raw); + } else { + buffer_copy_string_buffer(srv->tmp_buf, con->uri.path_raw); + buffer_urldecode_path(srv->tmp_buf); + buffer_path_simplify(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, COMP_HTTP_URL); /* 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; + } + + if (con->request.http_method == HTTP_METHOD_OPTIONS && + con->uri.path->ptr[0] == '*' && con->uri.path_raw->ptr[1] == '\0') { + /* option requests are handled directly without checking of the path */ + + response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST")); + + con->http_status = 200; + con->file_finished = 1; + + return HANDLER_FINISHED; + } + + /*** + * + * 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); + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- before doc_root"); + 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); + } + /* 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) + * */ + 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; + } + + /* MacOS X and Windows can't distiguish between upper and lower-case + * + * convert to lower-case + */ + if (con->conf.force_lowercase_filenames) { + buffer_to_lower(con->physical.rel_path); + } + + /* the docroot plugins might set the servername, if they don't we take http-host */ + if (buffer_is_empty(con->server_name)) { + buffer_copy_string_buffer(con->server_name, con->uri.authority); + } + + /** + * 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); + buffer_copy_string_buffer(con->physical.basedir, con->physical.path); + if (con->physical.rel_path->used && + 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", "-- after doc_root"); + 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); + } + + switch(r = plugins_call_handle_physical(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 (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); + } + } + + /* + * Noone catched away the file from normal path of execution yet (like mod_access) + * + * Go on and check of the file exists at all + */ + + if (con->mode == DIRECT) { + char *slash = NULL; + char *pathinfo = NULL; + int found = 0; + stat_cache_entry *sce = NULL; + + 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); + } + + if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { + /* file exists */ + + 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(sce->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 if (!S_ISREG(sce->st.st_mode)) { + /* any special handling of non-reg files ?*/ + + + } + } else { + switch (errno) { + case EACCES: + con->http_status = 403; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + + buffer_reset(con->physical.path); + return HANDLER_FINISHED; + case ENOENT: + con->http_status = 404; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- file not found"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + + buffer_reset(con->physical.path); + return HANDLER_FINISHED; + case ENOTDIR: + /* PATH_INFO ! :) */ + break; + default: + /* 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.path); + + do { + struct stat st; + + if (slash) { + buffer_copy_string_len(con->physical.path, srv->tmp_buf->ptr, slash - srv->tmp_buf->ptr); + } else { + buffer_copy_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 > con->physical.basedir->used - 2)); + + 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", "URI :", con->uri.path); + log_error_write(srv, __FILE__, __LINE__, "sb", "Pathinfo :", con->request.pathinfo); + } + } + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling subrequest"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + + /* call the handlers */ + switch(r = plugins_call_handle_subrequest_start(srv, con)) { + case HANDLER_GO_ON: + /* request was not handled */ + break; + case HANDLER_FINISHED: + default: + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- subrequest finished"); + } + + /* something strange happend */ + return r; + } + + /* if we are still here, no one wanted the file, status 403 is ok I think */ + + if (con->mode == DIRECT) { + con->http_status = 403; + + return HANDLER_FINISHED; + } + + } + + 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 0000000..c9ff234 --- /dev/null +++ b/src/response.h @@ -0,0 +1,19 @@ +#ifndef _RESPONSE_H_ +#define _RESPONSE_H_ + +#include <time.h> + +#include "server.h" + +int http_response_parse(server *srv, connection *con); +int http_response_write_header(server *srv, connection *con); + +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); +int http_response_handle_cachable(server *srv, connection *con, buffer * mtime); + +buffer * strftime_cache_get(server *srv, time_t last_mod); +#endif diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..f33100c --- /dev/null +++ b/src/server.c @@ -0,0 +1,1336 @@ +#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 "stat_cache.h" +#include "plugin.h" +#include "joblist.h" +#include "network_backends.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 + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifndef __sgi +/* IRIX doesn't like the alarm based time() optimization */ +/* #define USE_ALARM */ +#endif + +static volatile sig_atomic_t srv_shutdown = 0; +static volatile sig_atomic_t graceful_shutdown = 0; +static volatile sig_atomic_t handle_sig_alarm = 1; +static volatile 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 SIGINT: + if (graceful_shutdown) srv_shutdown = 1; + else graceful_shutdown = 1; + + break; + case SIGALRM: handle_sig_alarm = 1; break; + case SIGHUP: handle_sig_hup = 1; break; + case SIGCHLD: break; + } +} +#elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION) +static void signal_handler(int sig) { + switch (sig) { + case SIGTERM: srv_shutdown = 1; break; + case SIGINT: + if (graceful_shutdown) srv_shutdown = 1; + else graceful_shutdown = 1; + + break; + case SIGALRM: handle_sig_alarm = 1; break; + case SIGHUP: handle_sig_hup = 1; break; + case SIGCHLD: 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 (0 != fork()) exit(0); + + if (-1 == setsid()) exit(0); + + signal(SIGHUP, SIG_IGN); + + if (0 != fork()) exit(0); + + if (0 != chdir("/")) exit(0); + + 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(errorlog_buf); + CLEAN(response_range); + CLEAN(tmp_buf); + srv->empty_string = buffer_init_string(""); + CLEAN(cond_check_buf); + + CLEAN(srvconf.errorlog_file); + CLEAN(srvconf.groupname); + CLEAN(srvconf.username); + CLEAN(srvconf.changeroot); + CLEAN(srvconf.bindhost); + 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); + CLEAN(status); +#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->srvconf.modules = array_init(); + srv->srvconf.modules_dir = buffer_init_string(LIBRARY_DIR); + srv->srvconf.network_backend = buffer_init(); + srv->srvconf.upload_tempdirs = array_init(); + + /* use syslog */ + srv->errorlog_fd = -1; + srv->errorlog_mode = ERRORLOG_STDERR; + + srv->split_vals = array_init(); + + 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); + } + +#define CLEAN(x) \ + buffer_free(srv->x); + + CLEAN(response_header); + CLEAN(parse_full_path); + CLEAN(ts_debug_str); + CLEAN(ts_date_str); + CLEAN(errorlog_buf); + CLEAN(response_range); + CLEAN(tmp_buf); + CLEAN(empty_string); + CLEAN(cond_check_buf); + + CLEAN(srvconf.errorlog_file); + CLEAN(srvconf.groupname); + CLEAN(srvconf.username); + CLEAN(srvconf.changeroot); + CLEAN(srvconf.bindhost); + CLEAN(srvconf.event_handler); + CLEAN(srvconf.pid_file); + CLEAN(srvconf.modules_dir); + + CLEAN(tmp_chunk_len); +#undef CLEAN + +#if 0 + fdevent_unregister(srv->ev, srv->fd); +#endif + fdevent_free(srv->ev); + + free(srv->conns); + + if (srv->config_storage) { + for (i = 0; i < srv->config_context->used; i++) { + specific_config *s = srv->config_storage[i]; + + if (!s) continue; + + buffer_free(s->document_root); + buffer_free(s->server_name); + buffer_free(s->server_tag); + buffer_free(s->ssl_pemfile); + buffer_free(s->ssl_ca_file); + buffer_free(s->error_handler); + buffer_free(s->errorfile_prefix); + 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); + CLEAN(status); +#undef CLEAN + + joblist_free(srv, srv->joblist); + fdwaitqueue_free(srv, srv->fdwaitqueue); + + if (srv->stat_cache) { + stat_cache_free(srv->stat_cache); + } + + array_free(srv->srvconf.modules); + array_free(srv->split_vals); + + free(srv); +} + +static void show_version (void) { +#ifdef USE_OPENSSL +# define TEXT_SSL " (ssl)" +#else +# define TEXT_SSL +#endif + char *b = PACKAGE_NAME "-" 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_features (void) { + show_version(); + printf("\nEvent Handlers:\n\n%s", + +#ifdef USE_SELECT + "\t+ select (generic)\n" +#else + "\t- select (generic)\n" +#endif +#ifdef USE_POLL + "\t+ poll (Unix)\n" +#else + "\t- poll (Unix)\n" +#endif +#ifdef USE_LINUX_SIGIO + "\t+ rt-signals (Linux 2.4+)\n" +#else + "\t- rt-signals (Linux 2.4+)\n" +#endif +#ifdef USE_LINUX_EPOLL + "\t+ epoll (Linux 2.6)\n" +#else + "\t- epoll (Linux 2.6)\n" +#endif +#ifdef USE_SOLARIS_DEVPOLL + "\t+ /dev/poll (Solaris)\n" +#else + "\t- /dev/poll (Solaris)\n" +#endif +#ifdef USE_FREEBSD_KQUEUE + "\t+ kqueue (FreeBSD)\n" +#else + "\t- kqueue (FreeBSD)\n" +#endif + "\nNetwork handler:\n\n" +#if defined(USE_LINUX_SENDFILE) || defined(USE_FREEBSD_SENDFILE) || defined(USE_SOLARIS_SENDFILEV) || defined(USE_AIX_SENDFILE) + "\t+ sendfile\n" +#else + #ifdef USE_WRITEV + "\t+ writev\n" + #else + "\t+ write\n" + #endif + #ifdef USE_MMAP + "\t+ mmap support\n" + #else + "\t- mmap support\n" + #endif +#endif + "\nFeatures:\n\n" +#ifdef HAVE_IPV6 + "\t+ IPv6 support\n" +#else + "\t- IPv6 support\n" +#endif +#if defined HAVE_ZLIB_H && defined HAVE_LIBZ + "\t+ zlib support\n" +#else + "\t- zlib support\n" +#endif +#if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2 + "\t+ bzip2 support\n" +#else + "\t- bzip2 support\n" +#endif +#ifdef HAVE_LIBCRYPT + "\t+ crypt support\n" +#else + "\t- crypt support\n" +#endif +#ifdef USE_PAM + "\t+ PAM support\n" +#else + "\t- PAM support\n" +#endif +#ifdef USE_OPENSSL + "\t+ SSL Support\n" +#else + "\t- SSL Support\n" +#endif +#ifdef HAVE_LIBPCRE + "\t+ PCRE support\n" +#else + "\t- PCRE support\n" +#endif +#ifdef HAVE_MYSQL + "\t+ mySQL support\n" +#else + "\t- mySQL support\n" +#endif +#if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER) + "\t+ LDAP support\n" +#else + "\t- LDAP support\n" +#endif +#ifdef HAVE_MEMCACHE_H + "\t+ memcached support\n" +#else + "\t- memcached support\n" +#endif +#ifdef HAVE_FAM_H + "\t+ FAM support\n" +#else + "\t- FAM support\n" +#endif +#ifdef HAVE_LUA_H + "\t+ LUA support\n" +#else + "\t- LUA support\n" +#endif +#ifdef HAVE_LIBXML_H + "\t+ xml support\n" +#else + "\t- xml support\n" +#endif +#ifdef HAVE_SQLITE3_H + "\t+ SQLite support\n" +#else + "\t- SQLite support\n" +#endif +#ifdef HAVE_GDBM_H + "\t+ GDBM support\n" +#else + "\t- GDBM support\n" +#endif + "\n" + ); +} + +static void show_help (void) { +#ifdef USE_OPENSSL +# define TEXT_SSL " (ssl)" +#else +# define TEXT_SSL +#endif + char *b = PACKAGE_NAME "-" PACKAGE_VERSION TEXT_SSL " ("__DATE__ " " __TIME__ ")" \ +" - a light and fast webserver\n" \ +"usage:\n" \ +" -f <name> filename of the config-file\n" \ +" -m <name> module directory (default: "LIBRARY_DIR")\n" \ +" -p print the parsed config-file in internal form, and exit\n" \ +" -t test the config-file, and exit\n" \ +" -D don't go to background (default: go to background)\n" \ +" -v show version\n" \ +" -V show compile-time features\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 print_config = 0; + int test_config = 0; + 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 happen?\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:m:hvVDpt"))) { + switch(o) { + case 'f': + if (config_read(srv, optarg)) { + server_free(srv); + return -1; + } + break; + case 'm': + buffer_copy_string(srv->srvconf.modules_dir, optarg); + break; + case 'p': print_config = 1; break; + case 't': test_config = 1; break; + case 'D': srv->srvconf.dont_daemonize = 1; break; + case 'v': show_version(); return 0; + case 'V': show_features(); 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; + } + + if (print_config) { + data_unset *dc = srv->config_context->data[0]; + if (dc) { + dc->print(dc, 0); + fprintf(stderr, "\n"); + } else { + /* shouldn't happend */ + fprintf(stderr, "global config not found\n"); + } + } + + if (test_config) { + printf("Syntax OK\n"); + } + + if (test_config || print_config) { + server_free(srv); + return 0; + } + + /* 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 (srv->event_handler == FDEVENT_HANDLER_SELECT) { + /* select limits itself + * + * as it is a hard limit and will lead to a segfault we add some safety + * */ + srv->max_fds = FD_SETSIZE - 200; + } else { + srv->max_fds = 4096; + } + + 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; + } + } + + /* #372: solaris need some fds extra for devpoll */ + if (rlim.rlim_cur > 10) rlim.rlim_cur -= 10; + + if (srv->event_handler == FDEVENT_HANDLER_SELECT) { + srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ? rlim.rlim_cur : FD_SETSIZE - 200; + } else { + srv->max_fds = rlim.rlim_cur; + } + + /* set core file rlimit, if enable_cores is set */ + if (use_rlimit && srv->srvconf.enable_cores && getrlimit(RLIMIT_CORE, &rlim) == 0) { + rlim.rlim_cur = rlim.rlim_max; + setrlimit(RLIMIT_CORE, &rlim); + } +#endif + if (srv->event_handler == FDEVENT_HANDLER_SELECT) { + /* don't raise the limit above FD_SET_SIZE */ + if (srv->max_fds > FD_SETSIZE - 200) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "can't raise max filedescriptors above", FD_SETSIZE - 200, + "if event-handler is 'select'. Use 'poll' or something else or reduce server.max-fds."); + 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 && srv->srvconf.groupname->used) + initgroups(srv->srvconf.username->ptr, grp->gr_gid); + if (srv->srvconf.username->used) setuid(pwd->pw_uid); +#endif +#ifdef HAVE_PRCTL + if (srv->srvconf.enable_cores) { + prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + } +#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; + } + + if (srv->event_handler == FDEVENT_HANDLER_SELECT) { + srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ? rlim.rlim_cur : FD_SETSIZE - 200; + } else { + srv->max_fds = rlim.rlim_cur; + } + + /* set core file rlimit, if enable_cores is set */ + if (srv->srvconf.enable_cores && getrlimit(RLIMIT_CORE, &rlim) == 0) { + rlim.rlim_cur = rlim.rlim_max; + setrlimit(RLIMIT_CORE, &rlim); + } + +#endif + if (srv->event_handler == FDEVENT_HANDLER_SELECT) { + /* don't raise the limit above FD_SET_SIZE */ + if (srv->max_fds > FD_SETSIZE - 200) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "can't raise max filedescriptors above", FD_SETSIZE - 200, + "if event-handler is 'select'. Use 'poll' or something else or reduce server.max-fds."); + return -1; + } + } + + if (0 != network_init(srv)) { + plugins_free(srv); + server_free(srv); + + return -1; + } + } + + /* set max-conns */ + if (srv->srvconf.max_conns > srv->max_fds) { + /* we can't have more connections than max-fds */ + srv->max_conns = srv->max_fds; + } else if (srv->srvconf.max_conns) { + /* otherwise respect the wishes of the user */ + srv->max_conns = srv->srvconf.max_conns; + } else { + /* or use the default */ + srv->max_conns = srv->max_fds; + } + + 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; + } + +#ifdef HAVE_FORK + /* network is up, let's deamonize ourself */ + if (srv->srvconf.dont_daemonize == 0) daemonize(); +#endif + + srv->gid = getgid(); + srv->uid = getuid(); + + /* 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 (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; i < srv->config_context->used; i++) { + array *config = ((data_config *)srv->config_context->data[i])->value; + size_t j; + + for (j = 0; config && j < config->used; j++) { + data_unset *du = config->data[j]; + + /* all var.* is known as user defined variable */ + if (strncmp(du->key->ptr, "var.", sizeof("var.") - 1) == 0) { + continue; + } + + 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; + } + + 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; + } + + +#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(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGALRM, &act, NULL); + sigaction(SIGCHLD, &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); + signal(SIGCHLD, signal_handler); + signal(SIGINT, 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__, "s", "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 (srv_shutdown) { + kill(0, SIGTERM); + } + if (!child) return 0; + } +#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; + } + /* + * kqueue() is called here, select resets its internals, + * all server sockets get their handlers + * + * */ + if (0 != network_register_fdevents(srv)) { + plugins_free(srv); + network_close(srv); + server_free(srv); + + return -1; + } + + /* might fail if user is using fam (not gamin) and famd isn't running */ + if (NULL == (srv->stat_cache = stat_cache_init())) { + log_error_write(srv, __FILE__, __LINE__, "s", + "stat-cache could not be setup, dieing."); + return -1; + } + +#ifdef HAVE_FAM_H + /* setup FAM */ + if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { + if (0 != FAMOpen2(srv->stat_cache->fam, "lighttpd")) { + log_error_write(srv, __FILE__, __LINE__, "s", + "could not open a fam connection, dieing."); + return -1; + } +#ifdef HAVE_FAMNOEXISTS + FAMNoExists(srv->stat_cache->fam); +#endif + + srv->stat_cache->fam_fcce_ndx = -1; + fdevent_register(srv->ev, FAMCONNECTION_GETFD(srv->stat_cache->fam), stat_cache_handle_fdevent, NULL); + fdevent_event_add(srv->ev, &(srv->stat_cache->fam_fcce_ndx), FAMCONNECTION_GETFD(srv->stat_cache->fam), FDEVENT_IN); + } +#endif + + + /* get the current number of FDs */ + srv->cur_fds = open("/dev/null", O_RDONLY); + close(srv->cur_fds); + + for (i = 0; i < srv->srv_sockets.used; i++) { + server_socket *srv_socket = srv->srv_sockets.ptr[i]; + if (-1 == fdevent_fcntl_set(srv->ev, srv_socket->fd)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed:", strerror(errno)); + return -1; + } + } + + /* 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; + + /* cleanup stat-cache */ + stat_cache_trigger_cleanup(srv); + /** + * 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__, "sbsosds", + "NOTE: a request for", + con->request.uri, + "timed out after writing", + con->bytes_written, + "bytes. We waited", + (int)con->conf.max_write_idle, + "seconds. If this a problem increase server.max-write-idle"); +#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->conf.kbytes_per_second == 0 || + ((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"); + } + } + + if (srv->sockets_disabled) { + /* our server sockets are disabled, why ? */ + + if ((srv->cur_fds + srv->want_fds < srv->max_fds * 0.8) && /* we have enough unused fds */ + (srv->conns->used < srv->max_conns * 0.9) && + (0 == graceful_shutdown)) { + 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 again"); + + srv->sockets_disabled = 0; + } + } else { + if ((srv->cur_fds + srv->want_fds > srv->max_fds * 0.9) || /* out of fds */ + (srv->conns->used > srv->max_conns) || /* out of connections */ + (graceful_shutdown)) { /* graceful_shutdown */ + + /* 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); + + if (graceful_shutdown) { + /* we don't want this socket anymore, + * + * closing it right away will make it possible for + * the next lighttpd to take over (graceful restart) + * */ + + fdevent_unregister(srv->ev, srv_socket->fd); + close(srv_socket->fd); + srv_socket->fd = -1; + + /* network_close() will cleanup after us */ + } + } + + if (graceful_shutdown) { + log_error_write(srv, __FILE__, __LINE__, "s", "[note] graceful shutdown started"); + } else if (srv->conns->used > srv->max_conns) { + log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, connection limit reached"); + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds"); + } + + srv->sockets_disabled = 1; + } + } + + if (graceful_shutdown && srv->conns->used == 0) { + /* we are in graceful shutdown phase and all connections are closed + * we are ready to terminate without harming anyone */ + srv_shutdown = 1; + } + + /* 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: + case HANDLER_WAIT_FOR_FD: + 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 != EACCES && 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); + connections_free(srv); + plugins_free(srv); + server_free(srv); + + return 0; +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..bca2d52 --- /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 0000000..f0c6354 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,44 @@ +#ifndef _LIGHTTPD_SETTINGS_H_ +#define _LIGHTTPD_SETTINGS_H_ + +#define BV(x) (1 << x) + +#define INET_NTOP_CACHE_MAX 4 +#define FILE_CACHE_MAX 16 + +/** + * 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 0000000..99fc31a --- /dev/null +++ b/src/spawn-fcgi.c @@ -0,0 +1,431 @@ +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#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 pid_fd, int nofork) { + 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; + int val; + + 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; + } + + val = 1; + if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 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; + } + + if (!nofork) { + child = fork(); + } else { + child = 0; + } + + switch (child) { + case 0: { + char cgi_childs[64]; + char *b; + + 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); + + /* fork and replace shell */ + b = malloc(strlen("exec ") + strlen(appPath) + 1); + strcpy(b, "exec "); + strcat(b, appPath); + + /* exec the cgi */ + execl("/bin/sh", "sh", "-c", b, 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); + + /* write pid file */ + if (pid_fd != -1) { + /* assume a 32bit pid_t */ + char pidbuf[12]; + + snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child); + + write(pid_fd, pidbuf, strlen(pidbuf)); + close(pid_fd); + pid_fd = -1; + } + + 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" "-" PACKAGE_VERSION \ +" - spawns fastcgi processes\n" +; + write(1, b, strlen(b)); +} + +void show_help () { + char *b = "spawn-fcgi" "-" PACKAGE_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" \ +" -P <path> name of PID-file for spawed process\n" \ +" -n no fork (for daemontools)\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, *pid_file = NULL; + unsigned short port = 0; + int child_count = 5; + int i_am_root, o; + int pid_fd = -1; + int nofork = 0; + + i_am_root = (getuid() == 0); + + while(-1 != (o = getopt(argc, argv, "c:f:g:hnp:u:vC:s:P:"))) { + 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 'n': nofork = 1; break; + case 'P': pid_file = optarg; /* PID file */ 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 (pid_file && + (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))) { + struct stat st; + if (errno != EEXIST) { + fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n", + __FILE__, __LINE__, + pid_file, strerror(errno)); + + return -1; + } + + /* ok, file exists */ + + if (0 != stat(pid_file, &st)) { + fprintf(stderr, "%s.%d: stating pid-file '%s' failed: %s\n", + __FILE__, __LINE__, + pid_file, strerror(errno)); + + return -1; + } + + /* is it a regular file ? */ + + if (!S_ISREG(st.st_mode)) { + fprintf(stderr, "%s.%d: pid-file exists and isn't regular file: '%s'\n", + __FILE__, __LINE__, + pid_file); + + return -1; + } + + if (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { + fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n", + __FILE__, __LINE__, + pid_file, strerror(errno)); + + 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, pid_fd, nofork); +} +#else +int main() { + return -1; +} +#endif diff --git a/src/splaytree.c b/src/splaytree.c new file mode 100644 index 0000000..3a80910 --- /dev/null +++ b/src/splaytree.c @@ -0,0 +1,210 @@ +/* + An implementation of top-down splaying with sizes + D. Sleator <sleator@cs.cmu.edu>, January 1994. + + This extends top-down-splay.c to maintain a size field in each node. + This is the number of nodes in the subtree rooted there. This makes + it possible to efficiently compute the rank of a key. (The rank is + the number of nodes to the left of the given key.) It it also + possible to quickly find the node of a given rank. Both of these + operations are illustrated in the code below. The remainder of this + introduction is taken from top-down-splay.c. + + "Splay trees", or "self-adjusting search trees" are a simple and + efficient data structure for storing an ordered set. The data + structure consists of a binary tree, with no additional fields. It + allows searching, insertion, deletion, deletemin, deletemax, + splitting, joining, and many other operations, all with amortized + logarithmic performance. Since the trees adapt to the sequence of + requests, their performance on real access patterns is typically even + better. Splay trees are described in a number of texts and papers + [1,2,3,4]. + + The code here is adapted from simple top-down splay, at the bottom of + page 669 of [2]. It can be obtained via anonymous ftp from + spade.pc.cs.cmu.edu in directory /usr/sleator/public. + + The chief modification here is that the splay operation works even if the + item being splayed is not in the tree, and even if the tree root of the + tree is NULL. So the line: + + t = splay(i, t); + + causes it to search for item with key i in the tree rooted at t. If it's + there, it is splayed to the root. If it isn't there, then the node put + at the root is the last one before NULL that would have been reached in a + normal binary search for i. (It's a neighbor of i in the tree.) This + allows many other operations to be easily implemented, as shown below. + + [1] "Data Structures and Their Algorithms", Lewis and Denenberg, + Harper Collins, 1991, pp 243-251. + [2] "Self-adjusting Binary Search Trees" Sleator and Tarjan, + JACM Volume 32, No 3, July 1985, pp 652-686. + [3] "Data Structure and Algorithm Analysis", Mark Weiss, + Benjamin Cummins, 1992, pp 119-130. + [4] "Data Structures, Algorithms, and Performance", Derick Wood, + Addison-Wesley, 1993, pp 367-375 +*/ + +#include <stdlib.h> +#include <assert.h> +#include "splaytree.h" + +#define compare(i,j) ((i)-(j)) +/* This is the comparison. */ +/* Returns <0 if i<j, =0 if i=j, and >0 if i>j */ + +#define node_size splaytree_size + +/* Splay using the key i (which may or may not be in the tree.) + * The starting root is t, and the tree used is defined by rat + * size fields are maintained */ +splay_tree * splaytree_splay (splay_tree *t, int i) { + splay_tree N, *l, *r, *y; + int comp, root_size, l_size, r_size; + + if (t == NULL) return t; + N.left = N.right = NULL; + l = r = &N; + root_size = node_size(t); + l_size = r_size = 0; + + for (;;) { + comp = compare(i, t->key); + if (comp < 0) { + if (t->left == NULL) break; + if (compare(i, t->left->key) < 0) { + y = t->left; /* rotate right */ + t->left = y->right; + y->right = t; + t->size = node_size(t->left) + node_size(t->right) + 1; + t = y; + if (t->left == NULL) break; + } + r->left = t; /* link right */ + r = t; + t = t->left; + r_size += 1+node_size(r->right); + } else if (comp > 0) { + if (t->right == NULL) break; + if (compare(i, t->right->key) > 0) { + y = t->right; /* rotate left */ + t->right = y->left; + y->left = t; + t->size = node_size(t->left) + node_size(t->right) + 1; + t = y; + if (t->right == NULL) break; + } + l->right = t; /* link left */ + l = t; + t = t->right; + l_size += 1+node_size(l->left); + } else { + break; + } + } + l_size += node_size(t->left); /* Now l_size and r_size are the sizes of */ + r_size += node_size(t->right); /* the left and right trees we just built.*/ + t->size = l_size + r_size + 1; + + l->right = r->left = NULL; + + /* The following two loops correct the size fields of the right path */ + /* from the left child of the root and the right path from the left */ + /* child of the root. */ + for (y = N.right; y != NULL; y = y->right) { + y->size = l_size; + l_size -= 1+node_size(y->left); + } + for (y = N.left; y != NULL; y = y->left) { + y->size = r_size; + r_size -= 1+node_size(y->right); + } + + l->right = t->left; /* assemble */ + r->left = t->right; + t->left = N.right; + t->right = N.left; + + return t; +} + +splay_tree * splaytree_insert(splay_tree * t, int i, void *data) { +/* Insert key i into the tree t, if it is not already there. */ +/* Return a pointer to the resulting tree. */ + splay_tree * new; + + if (t != NULL) { + t = splaytree_splay(t, i); + if (compare(i, t->key)==0) { + return t; /* it's already there */ + } + } + new = (splay_tree *) malloc (sizeof (splay_tree)); + assert(new); + if (t == NULL) { + new->left = new->right = NULL; + } else if (compare(i, t->key) < 0) { + new->left = t->left; + new->right = t; + t->left = NULL; + t->size = 1+node_size(t->right); + } else { + new->right = t->right; + new->left = t; + t->right = NULL; + t->size = 1+node_size(t->left); + } + new->key = i; + new->data = data; + new->size = 1 + node_size(new->left) + node_size(new->right); + return new; +} + +splay_tree * splaytree_delete(splay_tree *t, int i) { +/* Deletes i from the tree if it's there. */ +/* Return a pointer to the resulting tree. */ + splay_tree * x; + int tsize; + + if (t==NULL) return NULL; + tsize = t->size; + t = splaytree_splay(t, i); + if (compare(i, t->key) == 0) { /* found it */ + if (t->left == NULL) { + x = t->right; + } else { + x = splaytree_splay(t->left, i); + x->right = t->right; + } + free(t); + if (x != NULL) { + x->size = tsize-1; + } + return x; + } else { + return t; /* It wasn't there */ + } +} + +splay_tree *find_rank(int r, splay_tree *t) { +/* Returns a pointer to the node in the tree with the given rank. */ +/* Returns NULL if there is no such node. */ +/* Does not change the tree. To guarantee logarithmic behavior, */ +/* the node found here should be splayed to the root. */ + int lsize; + if ((r < 0) || (r >= node_size(t))) return NULL; + for (;;) { + lsize = node_size(t->left); + if (r < lsize) { + t = t->left; + } else if (r > lsize) { + r = r - lsize -1; + t = t->right; + } else { + return t; + } + } +} + + diff --git a/src/splaytree.h b/src/splaytree.h new file mode 100644 index 0000000..98e4234 --- /dev/null +++ b/src/splaytree.h @@ -0,0 +1,24 @@ +#ifndef _SPLAY_TREE_H_ +#define _SPLAY_TREE_H_ + +typedef struct tree_node { + struct tree_node * left, * right; + int key; + int size; /* maintained to be the number of nodes rooted here */ + + void *data; +} splay_tree; + + +splay_tree * splaytree_splay (splay_tree *t, int key); +splay_tree * splaytree_insert(splay_tree *t, int key, void *data); +splay_tree * splaytree_delete(splay_tree *t, int key); +splay_tree * splaytree_size(splay_tree *t); + +#define splaytree_size(x) (((x)==NULL) ? 0 : ((x)->size)) +/* This macro returns the size of a node. Unlike "x->size", */ +/* it works even if x=NULL. The test could be avoided by using */ +/* a special version of NULL which was a real node with size 0. */ + + +#endif diff --git a/src/stat_cache.c b/src/stat_cache.c new file mode 100644 index 0000000..148f4c8 --- /dev/null +++ b/src/stat_cache.c @@ -0,0 +1,668 @@ +#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 <assert.h> + +#include "log.h" +#include "stat_cache.h" +#include "fdevent.h" +#include "etag.h" + +#ifdef HAVE_ATTR_ATTRIBUTES_H +#include <attr/attributes.h> +#endif + +#ifdef HAVE_FAM_H +# include <fam.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 + +#if 0 +/* enables debug code for testing if all nodes in the stat-cache as accessable */ +#define DEBUG_STAT_CACHE +#endif + +/* + * stat-cache + * + * we cache the stat() calls in our own storage + * the directories are cached in FAM + * + * if we get a change-event from FAM, we increment the version in the FAM->dir mapping + * + * if the stat()-cache is queried we check if the version id for the directory is the + * same and return immediatly. + * + * + * What we need: + * + * - for each stat-cache entry we need a fast indirect lookup on the directory name + * - for each FAMRequest we have to find the version in the directory cache (index as userdata) + * + * stat <<-> directory <-> FAMRequest + * + * if file is deleted, directory is dirty, file is rechecked ... + * if directory is deleted, directory mapping is removed + * + * */ + +#ifdef HAVE_FAM_H +typedef struct { + FAMRequest *req; + FAMConnection *fc; + + buffer *name; + + int version; +} fam_dir_entry; +#endif + +/* the directory name is too long to always compare on it + * - we need a hash + * - the hash-key is used as sorting criteria for a tree + * - a splay-tree is used as we can use the caching effect of it + */ + +/* we want to cleanup the stat-cache every few seconds, let's say 10 + * + * - remove entries which are outdated since 30s + * - remove entries which are fresh but havn't been used since 60s + * - if we don't have a stat-cache entry for a directory, release it from the monitor + */ + +#ifdef DEBUG_STAT_CACHE +typedef struct { + int *ptr; + + size_t used; + size_t size; +} fake_keys; + +static fake_keys ctrl; +#endif + +stat_cache *stat_cache_init(void) { + stat_cache *fc = NULL; + + fc = calloc(1, sizeof(*fc)); + + fc->dir_name = buffer_init(); +#ifdef HAVE_FAM_H + fc->fam = calloc(1, sizeof(*fc->fam)); +#endif + +#ifdef DEBUG_STAT_CACHE + ctrl.size = 0; +#endif + + return fc; +} + +static stat_cache_entry * stat_cache_entry_init(void) { + stat_cache_entry *sce = NULL; + + sce = calloc(1, sizeof(*sce)); + + sce->name = buffer_init(); + sce->etag = buffer_init(); + sce->content_type = buffer_init(); + + return sce; +} + +static void stat_cache_entry_free(void *data) { + stat_cache_entry *sce = data; + if (!sce) return; + + buffer_free(sce->etag); + buffer_free(sce->name); + buffer_free(sce->content_type); + + free(sce); +} + +#ifdef HAVE_FAM_H +static fam_dir_entry * fam_dir_entry_init(void) { + fam_dir_entry *fam_dir = NULL; + + fam_dir = calloc(1, sizeof(*fam_dir)); + + fam_dir->name = buffer_init(); + + return fam_dir; +} + +static void fam_dir_entry_free(void *data) { + fam_dir_entry *fam_dir = data; + + if (!fam_dir) return; + + FAMCancelMonitor(fam_dir->fc, fam_dir->req); + + buffer_free(fam_dir->name); + free(fam_dir->req); + + free(fam_dir); +} +#endif + +void stat_cache_free(stat_cache *sc) { + while (sc->files) { + int osize; + splay_tree *node = sc->files; + + osize = sc->files->size; + + stat_cache_entry_free(node->data); + sc->files = splaytree_delete(sc->files, node->key); + + assert(osize - 1 == splaytree_size(sc->files)); + } + + buffer_free(sc->dir_name); + +#ifdef HAVE_FAM_H + while (sc->dirs) { + int osize; + splay_tree *node = sc->dirs; + + osize = sc->dirs->size; + + fam_dir_entry_free(node->data); + sc->dirs = splaytree_delete(sc->dirs, node->key); + + if (osize == 1) { + assert(NULL == sc->dirs); + } else { + assert(osize == (sc->dirs->size + 1)); + } + } + + if (sc->fam) { + FAMClose(sc->fam); + free(sc->fam); + } +#endif + free(sc); +} + +#ifdef HAVE_XATTR +static int stat_cache_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 + +/* the famous DJB hash function for strings */ +static uint32_t hashme(buffer *str) { + uint32_t hash = 5381; + const char *s; + for (s = str->ptr; *s; s++) { + hash = ((hash << 5) + hash) + *s; + } + + hash &= ~(1 << 31); /* strip the highest bit */ + + return hash; +} + +#ifdef HAVE_FAM_H +handler_t stat_cache_handle_fdevent(void *_srv, void *_fce, int revent) { + size_t i; + server *srv = _srv; + stat_cache *sc = srv->stat_cache; + size_t events; + + UNUSED(_fce); + /* */ + + if ((revent & FDEVENT_IN) && + sc->fam) { + + events = FAMPending(sc->fam); + + for (i = 0; i < events; i++) { + FAMEvent fe; + fam_dir_entry *fam_dir; + splay_tree *node; + int ndx; + + FAMNextEvent(sc->fam, &fe); + + /* handle event */ + + switch(fe.code) { + case FAMChanged: + case FAMDeleted: + case FAMMoved: + /* if the filename is a directory remove the entry */ + + fam_dir = fe.userdata; + fam_dir->version++; + + /* file/dir is still here */ + if (fe.code == FAMChanged) break; + + buffer_copy_string(sc->dir_name, fe.filename); + + ndx = hashme(sc->dir_name); + + sc->dirs = splaytree_splay(sc->dirs, ndx); + node = sc->dirs; + + if (node && (node->key == ndx)) { + int osize = splaytree_size(sc->dirs); + + fam_dir_entry_free(node->data); + sc->dirs = splaytree_delete(sc->dirs, ndx); + + assert(osize - 1 == splaytree_size(sc->dirs)); + } + break; + default: + break; + } + } + } + + if (revent & FDEVENT_HUP) { + /* fam closed the connection */ + srv->stat_cache->fam_fcce_ndx = -1; + + fdevent_event_del(srv->ev, &(sc->fam_fcce_ndx), FAMCONNECTION_GETFD(sc->fam)); + fdevent_unregister(srv->ev, FAMCONNECTION_GETFD(sc->fam)); + + FAMClose(sc->fam); + free(sc->fam); + + sc->fam = NULL; + } + + return HANDLER_GO_ON; +} + +static int buffer_copy_dirname(buffer *dst, buffer *file) { + size_t i; + + if (buffer_is_empty(file)) return -1; + + for (i = file->used - 1; i+1 > 0; i--) { + if (file->ptr[i] == '/') { + buffer_copy_string_len(dst, file->ptr, i); + return 0; + } + } + + return -1; +} +#endif + +/*** + * + * + * + * returns: + * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file) + * - HANDLER_ERROR on stat() failed -> see errno for problem + */ + +handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) { +#ifdef HAVE_FAM_H + fam_dir_entry *fam_dir = NULL; + int dir_ndx = -1; + splay_tree *dir_node = NULL; +#endif + stat_cache_entry *sce = NULL; + stat_cache *sc; + struct stat st; + size_t k; + int fd; +#ifdef DEBUG_STAT_CACHE + size_t i; +#endif + + int file_ndx; + splay_tree *file_node = NULL; + + *ret_sce = NULL; + + /* + * check if the directory for this file has changed + */ + + sc = srv->stat_cache; + + file_ndx = hashme(name); + sc->files = splaytree_splay(sc->files, file_ndx); + +#ifdef DEBUG_STAT_CACHE + for (i = 0; i < ctrl.used; i++) { + if (ctrl.ptr[i] == file_ndx) break; + } +#endif + + if (sc->files && (sc->files->key == file_ndx)) { +#ifdef DEBUG_STAT_CACHE + /* it was in the cache */ + assert(i < ctrl.used); +#endif + + /* we have seen this file already and + * don't stat() it again in the same second */ + + file_node = sc->files; + + sce = file_node->data; + + /* check if the name is the same, we might have a collision */ + + if (buffer_is_equal(name, sce->name)) { + if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) { + if (sce->stat_ts == srv->cur_ts) { + *ret_sce = sce; + return HANDLER_GO_ON; + } + } + } else { + /* oops, a collision, + * + * file_node is used by the FAM check below to see if we know this file + * and if we can save a stat(). + * + * BUT, the sce is not reset here as the entry into the cache is ok, we + * it is just not pointing to our requested file. + * + * */ + + file_node = NULL; + } + } else { +#ifdef DEBUG_STAT_CACHE + if (i != ctrl.used) { + fprintf(stderr, "%s.%d: %08x was already inserted but not found in cache, %s\n", __FILE__, __LINE__, file_ndx, name->ptr); + } + assert(i == ctrl.used); +#endif + } + +#ifdef HAVE_FAM_H + /* dir-check */ + if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { + if (0 != buffer_copy_dirname(sc->dir_name, name)) { + SEGFAULT(); + } + + dir_ndx = hashme(sc->dir_name); + + sc->dirs = splaytree_splay(sc->dirs, dir_ndx); + + if (sc->dirs && (sc->dirs->key == dir_ndx)) { + dir_node = sc->dirs; + } + + if (dir_node && file_node) { + /* we found a file */ + + sce = file_node->data; + fam_dir = dir_node->data; + + if (fam_dir->version == sce->dir_version) { + /* the stat()-cache entry is still ok */ + + *ret_sce = sce; + return HANDLER_GO_ON; + } + } + } +#endif + + /* + * *lol* + * - open() + fstat() on a named-pipe results in a (intended) hang. + * - stat() if regualar file + open() to see if we can read from it is better + * + * */ + + if (-1 == stat(name->ptr, &st)) { + return HANDLER_ERROR; + } + + + if (S_ISREG(st.st_mode)) { + /* try to open the file to check if we can read it */ + if (-1 == (fd = open(name->ptr, O_RDONLY))) { + return HANDLER_ERROR; + } + close(fd); + } + + if (NULL == sce) { + int osize = 0; + + if (sc->files) { + osize = sc->files->size; + } + + sce = stat_cache_entry_init(); + buffer_copy_string_buffer(sce->name, name); + + sc->files = splaytree_insert(sc->files, file_ndx, sce); +#ifdef DEBUG_STAT_CACHE + if (ctrl.size == 0) { + ctrl.size = 16; + ctrl.used = 0; + ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr)); + } else if (ctrl.size == ctrl.used) { + ctrl.size += 16; + ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr)); + } + + ctrl.ptr[ctrl.used++] = file_ndx; + + assert(sc->files); + assert(sc->files->data == sce); + assert(osize + 1 == splaytree_size(sc->files)); +#endif + } + + sce->st = st; + sce->stat_ts = srv->cur_ts; + + /* catch the obvious symlinks + * + * this is not a secure check as we still have a race-condition between + * the stat() and the open. We can only solve this by + * 1. open() the file + * 2. fstat() the fd + * + * and keeping the file open for the rest of the time. But this can + * only be done at network level. + * + * */ + if (S_ISLNK(st.st_mode) && !con->conf.follow_symlink) { + return HANDLER_ERROR; + } + + if (S_ISREG(st.st_mode)) { + /* determine mimetype */ + buffer_reset(sce->content_type); + + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + buffer *type = ds->key; + + if (type->used == 0) continue; + + /* check if the right side is the same */ + if (type->used > name->used) continue; + + if (0 == strncasecmp(name->ptr + name->used - type->used, type->ptr, type->used - 1)) { + buffer_copy_string_buffer(sce->content_type, ds->value); + break; + } + } + etag_create(sce->etag, &(sce->st)); +#ifdef HAVE_XATTR + if (buffer_is_empty(sce->content_type)) { + stat_cache_attr_get(sce->content_type, name->ptr); + } +#endif + } + +#ifdef HAVE_FAM_H + if (sc->fam && + (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM)) { + /* is this directory already registered ? */ + if (!dir_node) { + fam_dir = fam_dir_entry_init(); + fam_dir->fc = sc->fam; + + buffer_copy_string_buffer(fam_dir->name, sc->dir_name); + + fam_dir->version = 1; + + fam_dir->req = calloc(1, sizeof(FAMRequest)); + + if (0 != FAMMonitorDirectory(sc->fam, fam_dir->name->ptr, + fam_dir->req, fam_dir)) { + + log_error_write(srv, __FILE__, __LINE__, "sbs", + "monitoring dir failed:", + fam_dir->name, + FamErrlist[FAMErrno]); + + fam_dir_entry_free(fam_dir); + } else { + int osize = 0; + + if (sc->dirs) { + osize = sc->dirs->size; + } + + sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir); + assert(sc->dirs); + assert(sc->dirs->data == fam_dir); + assert(osize == (sc->dirs->size - 1)); + } + } else { + fam_dir = dir_node->data; + } + + /* bind the fam_fc to the stat() cache entry */ + + if (fam_dir) { + sce->dir_version = fam_dir->version; + sce->dir_ndx = dir_ndx; + } + } +#endif + + *ret_sce = sce; + + return HANDLER_GO_ON; +} + +/** + * remove stat() from cache which havn't been stat()ed for + * more than 10 seconds + * + * + * walk though the stat-cache, collect the ids which are too old + * and remove them in a second loop + */ + +static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) { + stat_cache_entry *sce; + + if (!t) return 0; + + stat_cache_tag_old_entries(srv, t->left, keys, ndx); + stat_cache_tag_old_entries(srv, t->right, keys, ndx); + + sce = t->data; + + if (srv->cur_ts - sce->stat_ts > 2) { + keys[(*ndx)++] = t->key; + } + + return 0; +} + +int stat_cache_trigger_cleanup(server *srv) { + stat_cache *sc; + size_t max_ndx = 0, i; + int *keys; + + sc = srv->stat_cache; + + if (!sc->files) return 0; + + keys = calloc(1, sizeof(size_t) * sc->files->size); + + stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx); + + for (i = 0; i < max_ndx; i++) { + int ndx = keys[i]; + splay_tree *node; + + sc->files = splaytree_splay(sc->files, ndx); + + node = sc->files; + + if (node && (node->key == ndx)) { +#ifdef DEBUG_STAT_CACHE + size_t j; + int osize = splaytree_size(sc->files); + stat_cache_entry *sce = node->data; +#endif + stat_cache_entry_free(node->data); + sc->files = splaytree_delete(sc->files, ndx); + +#ifdef DEBUG_STAT_CACHE + for (j = 0; j < ctrl.used; j++) { + if (ctrl.ptr[j] == ndx) { + ctrl.ptr[j] = ctrl.ptr[--ctrl.used]; + break; + } + } + + assert(osize - 1 == splaytree_size(sc->files)); +#endif + } + } + + free(keys); + + return 0; +} diff --git a/src/stat_cache.h b/src/stat_cache.h new file mode 100644 index 0000000..e6f7796 --- /dev/null +++ b/src/stat_cache.h @@ -0,0 +1,13 @@ +#ifndef _FILE_CACHE_H_ +#define _FILE_CACHE_H_ + +#include "base.h" + +stat_cache *stat_cache_init(void); +void stat_cache_free(stat_cache *fc); + +handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **fce); +handler_t stat_cache_handle_fdevent(void *_srv, void *_fce, int revent); + +int stat_cache_trigger_cleanup(server *srv); +#endif diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 0000000..ecaadc1 --- /dev/null +++ b/src/stream.c @@ -0,0 +1,106 @@ +#include <sys/types.h> +#include <sys/stat.h> + +#include <unistd.h> +#include <fcntl.h> + +#include "stream.h" +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sys-mmap.h" + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +int stream_open(stream *f, buffer *fn) { + struct stat st; +#ifdef HAVE_MMAP + int fd; +#elif defined __WIN32 + HANDLE *fh, *mh; + void *p; +#endif + + f->start = NULL; + + if (-1 == stat(fn->ptr, &st)) { + return -1; + } + + f->size = st.st_size; + +#ifdef HAVE_MMAP + if (-1 == (fd = open(fn->ptr, O_RDONLY | O_BINARY))) { + 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 0000000..d4c9049 --- /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 0000000..94aaa19 --- /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 0000000..cc6e649 --- /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 |