diff options
author | Jan Kneschke <jan@kneschke.de> | 2005-02-20 14:27:00 +0000 |
---|---|---|
committer | Jan Kneschke <jan@kneschke.de> | 2005-02-20 14:27:00 +0000 |
commit | bcdc6a3bbcde8e66da41aa2311642e53f4fc7c9b (patch) | |
tree | a0536d23ba17a40c236fc3cd2a4a133110ae7501 | |
download | lighttpd-git-bcdc6a3bbcde8e66da41aa2311642e53f4fc7c9b.tar.gz |
moved everything below trunk/ and added branches/ and tags/
git-svn-id: svn://svn.lighttpd.net/lighttpd/trunk@30 152afb58-edef-0310-8abb-c4023f1b3aa9
333 files changed, 51339 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..80ea411a --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +jan kneschke <jan@kneschke.de> diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..3f699760 --- /dev/null +++ b/COPYING @@ -0,0 +1,31 @@ + + +Copyright (c) 2004, Jan Kneschke, incremental + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of the 'incremental' nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..9f29ce13 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3839 @@ +CVS-Version: $Id: ChangeLog,v 1.18 2004/04/09 22:08:19 weigon Exp $ + +Feature Requests: +* complex +- nested conditionals +- config file per host +- compressing dynamic content (filters) +- limit read-queue to 64k and use traffic-shaping cap + - if fastcgi/cgi are not ready ignore incoming FDEVENT_IN for a while + -> reduce memory usage +- userdir + suphp + -> internal config-hash +- don't strip // from PATHINFO +- TLS + +* moderate +- errorlog piping +- access and auth with host, net +- fastcgi-spawn + - suid spawn +- gracefull restart +- mod_accesslog_mysql +- mod_auth_mysql +- IPv6 for FastCGI +- man-page for spawn-fcgi +- server.base-directory + +* simple +- file-mapping for alias +- add header for broken requests if debug.request-header-on-error +- IPv6 for FastCGI +- hostnames for remote-FastCGI +- fix remote-fastcgi on freebsd + kqueue + - crash FDEVENT_OUT on FreeBSD +- EPERM on unlink +- ignore blank lines between requests + - handle \r\n at start of request +- cgi exec via shell +- added REMOTE_PORT, SERVER_ADDR to cgi/fastcgi +- REQUEST_URI to cgi +- don't set physical-file on 416-errors +- crash in read-post +- same config option twice -> aaa, aaa +- remove default port from Host: + +13.02.2005 17:33 +- mod_auth + + fix crash if require, realm or method are empty (Bug #5) + +13.02.2005 14:52 +- network + + handle EPIPE and ECONNRESET as 'client has closed connection' in writev() + (Bug #1) + +- macosx + + compile error on MacOS X due to missing environ (patch by Johan Sörensen) + (Bug #2) + +- indexfiles + + append the detected indexfile only once to uri.path (reported by Thomas + Seifert) + (Bug #3) + +06.02.2005 15:16 - 1.3.10 +- fastcgi + + display a error-message if a hostname if specified in fastcgi.server->host + we need an IP here + +- debug + + added debug.log-state-handling + +- spawn-fcgi + + accept a full commandline for spawning + +06.02.2005 12:50 +- fastcgi + + fixed openssl handling + +- network_freebsd_sendfilev + + gracefull handling of connections closed on client-side + removed debug-message + +06.02.2005 01:44 - 1.3.9 +- documentation + + added docs for SSL setup and mod_status + +- fastcgi + + fixed config handling on PowerPC for local-spawning + +05.02.2005 15:14 +- fastcgi + + added bin-environment to setup the environment of the spawned process + + added bin-copy-environment to copy only the specified set of options + from the old environment + + added handling of cmd-line options to bin-path + +- setenv + + fixed crashed in setenv.add-response-header + +04.02.2005 18:09 +- configure + + fixed docs for --with-mysql + +- fastcgi + + improved performance of building the header (drop strlen()) + +04.02.2005 01:59 +- cgi + + don't send file on error + + check if cgi-handler exists before executing it + + added support for nph-... + +02.02.2005 21:18 - pre-release +- request parsing + + handle invalid characters in URI + +02.02.2005 15:12 +- makefiles + + dropped unused header files from the distribution + +02.02.2005 14:18 +- fastcgi + + delete sockets on shutdown + +- http/1.1 + + adding option to disable http/1.1 + +01.02.2005 12:03 +- cygwin + + fixed plugins_load to use the right extensions again + removed mmap check + added ssl support + +01.02.2005 01:49 +- configure + + make check for valgrind.h covered by --with-valgrind + +- mod_localizer, mod_maps + + remove both plugins from the distribution + +- file-not-found + + handle file not found again + +30.01.2005 16:44 +- HEAD requests + + don't send content on dynamic HEAD requests with status 200 + +30.01.2005 15:16 - 1.3.8 +- network-handler + + remove debug output on writev() if the remote side closed the connection + +- directory index + + handle EACCES correctly + +29.01.2005 15:16 - pre-release +- mod_alias + + fixed mod_alias + pathinfo handling + +- mod_accesslog + + added access-log to syslog patch from allan + +28.01.2005 17:30 +- directory redirect without Host-header + + use server-ip instead of client-ip for the Location: + +- fastcgi + pathinfo + + if fastcgi-auth redirects to a directory which doesn't exist handle it + correctly (bug introduced in 1.3.8) + +- requesting directories + + clean physical.path if directory is requested and dir-listing is disabled + send 403 again (buf introduced in 1.3.8) + +28.01.2005 12:08 +- fastcgi + + ignore FDEVENT_HUP for unix-sockets as a simple read + timeout will do + the job anyway + +22.01.2005 20:28 - pre-release +- fastcgi + + send content and headers if authorizer mode is used + + use a new connection if connection is died to fastcgi + and we have not used it yet + +18.01.2005 21:21 - pre-release +- plugins + + added version-id to plugins to detect plugins which are not up-to-date + +16.01.2005 23:11 +- fastcgi + + fixed write-failed after crash of fastcgi-child + +16.01.2005 20:43 +- setenv + + fixed setenv.add-environment + +- fastcgi + + fixed authorizer + added testcases + +16.01.2005 17:40 - pre-release +- mod_status + + beautified mod_status + +- mod_setenv + + added setenv.add-environment + +- timeouts + + add timeout to read-post + +15.01.2005 12:57 +- debug + + added debug options to log + - missing files + - request header + - response header + - request handling + + added a more usefull error message for the status-code changes in the + request parser + +- server announcement + + set Server: header for dynamic content too + +- fastcgi + + fixed double free + + don't crash on FDEVENT_ERR + + added a comment for EAGAIN on connect() + +08.01.2005 17:45 +- ssl + + report an error if ssl.engine is enable but no ssl support compiled in + +08.01.2005 12:23 +- mod_status + + added request time to the output + + (late changelog) added host and filename to the output (fobax) + (late changelog) HTMLalized the output (fobax) + +06.01.2005 19:51 - pre-release +- error-handler + + let the error-handler handle 403 requests too + + make the error-handler setable by a module + +- error-pages + + reworked the error-page handling + +05.01.2005 13:10 +- keep-alive handling + + made sure that keep-alive is really handled correctly + +04.01.2005 17:02 +- mod_setenv + + added a module to added request and response headers on the fly + +- error-log + + send error log to syslog() if no errorlogfile is specified (again) + +02.01.2005 22:44 - pre-release +- response handling + + cut of body for status 301, 304 and 205 + +- buffer + + optimized all _hex functions (Silvan Minghetti) + +02.01.2005 20:32 +- fastcgi + + if bin-path is not specified, don't die (bug introduced in the last pre-rel) + +- auth + + if userfile is empty don't auth. + +02.01.2005 19:06 +- mod_compress + + fixed off by one if cache-dir is not set + +02.01.2005 16:10 +- conditional config + + fixed !~ and != + +- buffer + + copy empty buffers correctly + +31.12.2004 17:45 +- ipv6 + pidfile + + don't complain if we can't remove the pidfile (Silvan Minghetti) + + remove ipv6 option from the commandline of lighttpd doesn't support + ipv6 (Silvan Minghetti) + +31.12.2004 15:41 - pre-release +- kqueue + + simplified event handling (adam) + +- fastcgi + + fixed div-by-zero bugs in the adaptive process spawning + +- mysql-vhost + + added mysql-vhost (Christer Holgersson) + +30.12.2004 19:09 +- fastcgi + + added adaptive spawning of FastCGI processes + +- traffic shaping + + added traffic shaping per virtual server + +28.12.2004 23:26 +- traffic shaping + + added traffic shaping per connection + +25.12.2004 22:58 +- mod_status + + fixed status.url again (Timo) + +21.12.2004 11:29 +- configure + + added check for signal and select (compile fix for netbsd 1.4 and 1.5) + +11.12.2004 12:38 - 1.3.7 +- fastcgi + php + + retry to connect to another PHP child if one of them dies after + connect + +- cgi + multipart + + don't transform CONTENT_TYPE to HTTP_CONTENT_TYPE + +- debian + + more cleanup, updated changelog, added more deps and suggests + (Chris Brown) + +10.12.2004 22:33 +- event handler + + fixed crashes in kqueue + +10.12.2004 13:57 - pre-release +- mod_status + + fixed wraparound in total requests and total traffic + +- debian + + updated licence and packaging + +- security + + call setgroups() to get rid of all groups + +- ssl + + handle SSL_shutdown() == 0 correctly + + fixed openssl detection in configure + + fixed handling of chunked encoding + +- request handling + + handle Connection: keep-alive correctly (case as not ignored) + +21.11.2004 02:39 +- windows + + merged basic native windows port (compiles with mingw) + +20.11.2004 18:43 +- conditional + + ported + - cgi + - secdownload + - expire + - localizer + - usertrack + - status + - proxy + +- server-tag + + Server: ... can now be specified by server.tag = "..." + +- spawn-fcgi + + fixed typo in usage text + +- ssl + + fixed detection of libs and headers + +05.11.2004 16:01 +- fastcgi + + added more usefull error messages + +04.11.2004 23:01 +- ssi + + added support for ${...} + +03.11.2004 14:51 - 1.3.6 +- fastcgi + + added spawn-fcgi to the distribution + added spawn-local-fastcgi yourself ( bin-path ) + +03.11.2004 11:22 +- accesslog + + don't cycle accesslogs of external processes are used + +02.11.2004 15:34 +- fastcgi + + handle END-OF-REQUEST correctly if chunk-encoding is not used + +02.11.2004 10:53 +- internal redirects + + fixed handling of query strings in internal redirects for directories + +02.11.2004 09:54 - pre-release +- cgi + + add REMOTE_USER, suppress AUTHORIZATION + handle payloads > 4k + +- mod_alias + + fixed url checking + +- follow-symlink + + fixed config + +31.10.2004 11:30 - 1.3.5 +- writev + + fixed seg-fault in debug-message if write() fails and LFS is enabled + handle EINTR + +- sendfile linux + + handle EINTR + +31.10.2004 09:09 +- freebsd + + added missing header in joblist.c + fixed test-scripts for zsh + +30.10.2004 22:26 +- modules + + added mod_userdir and mod_alias + added docs for the new modules + +30.10.2004 19:52 +- porting + + added defines for MAP_FAILED for NetBSD 1.3.x + +30.10.2004 18:54 - pre-release +- pipelining + + fixed offset calculations + +- ipv6 + + IPv6 might be disabled at compile-time + +- rewrite + + close mem-leak + +- auth + + forgot to reset the global-config handler + +- symlink + + add option to disable follow-symlink + +- ssi + + added support for exec-cmd + +23.10.2004 - 1.3.4 +- max-fds + + set the upper limit of fds only if server.max-fds is set + +23.10.2004 13:49 +- accesslog + + use a shell to handle accesslog-pipes + +22.10.2004 17:00 +- accesslog + + added logging of user-supplied data via %{...}o and + X-LIGHTTPD-* header + +22.10.2004 14:57 - pre-release +- openwrt + + fixed configure-checks and Makefile.am's to build cleanly with a + cross-compiler + + builds cleanly for openwrt + +22.10.2004 13:03 +- out-of-fd + + improved the out-of-fd handler + +- cgi, fastcgi + + set SERVER_NAME to server.name or the value submitted by Host: + +- error-handler + + only set old status code if it wasn't set by a handler + +21.10.2004 22:36 - pre-release +- fastcgi + + don't crash on out-of-fd condition + +- out-of-fd + + try handle the out-of-fd condition in a sane way + +21.10.2004 15:03 +- mod_auth + + seperated auth.backend.*.userfile for plain, htpasswd and htdigest + + added 'digest-auth' against 'plain-backend' + + added auth.debug for debugging + +16.10.2004 10:18 - 1.3.3 +- mod_simple_vhost, mod_evhost + + conditional-ized + +- mod_rrdtool + + maintain the request-counter for each conditional-config (adam) + +14.10.2004 11:30 +- accesslogs + + cycle all access-logs + +- mod_rewrite + + tell the user to install pcre.h if he wants to use mod_rewrite + +10.10.2004 10:11 - pre-release +- error-handler + + added a error-handler for status 404 (server.error-handler-404) + +09.10.2004 16:28 - pre-release +- cgi + + added support for \n in headers + +- mod_auth + + added conditional auth + +01.10.2004 09:28 +- plugins + + fixed off by one error in plugin initialization (Mike) + related into a segfault on AMD64 + +30.09.2004 21:44 - 1.3.2 +- file-cache + + disabled the file-cache it was taken the wrong files from the cache + +30.09.2004 08:39 - 1.3.1 +- file-cache + + drop a unused file-cache entry after 10 seconds + reuse unused entries + +- request-parser + + accept IPv6 adresses in Host header + +- tests + + modified the scripts to work with zsh (check on Linux, Irix and FreeBSD) + +26.09.2004 12:28 +- comparission function + + file-cache has delivering the wrong entry if only the last character of + the filename differed and the filesize was the same. + +- cgi + cygwin + + cgi need s SYSTEMROOT environment + + +22.09.2004 08:55 +- network + + detect of file has been shrinked while we are sending it out and terminate + the connection if would run over the edge + +22.09.2004 07:56 +- mod rewrite, fastcgi, ... + + keep REQUEST_URI after rewrite + +21.09.2004 22:49 +- fastcgi authorizer + + fixed cleanup code (matt) + +21.09.2004 20:08 +- rrdtool + + rrdtool.db-name is now conditional + + fixed check if write() failed (adam) + +17.09.2004 17:50 - 1.3.0 +- rewrite + + added url.rewrite-final = ... + +17.09.2004 15:55 +- code cleanup + + integrated the fixes from cygwin into the main tree + +- kqueue + + init kqueue after daemonizing (broken since 12.09.2004 14:02) + +16.09.2004 21:00 +- cygwin + macosx + + finished the cygwin port + this port seems to fix the problems on macosx too + +12.09.2004 14:02 +- socket handling + + added support to handle more than one server socket + +11.09.2004 12:23 - 1.2.8 +- EINTR + + handle EINTR for linux-sendfile + +- configfile + + ignore an extra comma at the end of the array declaration + +11.09.2004 09:46 +- mod_proxy + + pass remote-addr as X-Forwarded-For to the real server behind the proxy + +- code cleanup + + moved all cut'n'paste versions of the inet_ntop cache to inet_ntop_cache.c + +- fcgi + + don't overwrite the fd in fcgi_establish connection if connect fails. this + results in various problem in other places. + +05.09.2004 09:46 +- file-cache + + cache the mimetype + +- last-modified + + don't complain if the If-Modified-Since contains a valid comment/option + like <timestamp>; length = ... + +05.09.2004 09:13 +- expires + + overwrite the Expire if it is set by a previous plugin + +- conditional config + + conditional config as disabled in 1.2.7 by accident + +04.09.2004 10:02 - 1.2.7 +- mod-proxy + + remove the \0 before the post content + +- cgi + + fixed hanging process if cgi-crash terminates to fast (before we read its + response) + +- extented attributes + + added xattr support, submitted by Ari + +29.08.2004 16:00 +- rrdtool + + moved the rrdtool support from mod_status into its own module mod_rrdtool + + rrdtool.binary = "/usr/bin/rrdtool" + rrdtool.db-name = "/var/www/lighttpd.rrd" + +29.08.2004 11:00 - pre-release +- timeouts + + server.max-keep-alive-requests = 0 replaces + server.use-keep-alive = "disable" + + added + server.max-keep-alive-idle + server.max-read-idle + server.max-write-idle + +- docs + + added a entry for each config-value into configuration.txt + added simple docs for + rewrite + redirect + compress + cgi + simple-vhost + +29.08.2004 10:05 +- config options + + complain if no configfile is specified + +- fastcgi + + removed stupid allocation bug which might cause a problem in really rare + cases + +26.08.2004 22:06 - 1.2.6 +- optimize + + use array_strcasecmp() in favour of strcasecmp() as it is slightly + faster. + + apply the case-insentive conversion also on the last character. (adam) + + sort the checked elements in request.c and filter apply the logic to + compare some less fields, if the header is not used. + + improved the config-patch function to use our internal buffer-compare + functions instead of strcmp + +22.08.2004 16:09 - pre-release +- cgi + + added missing cleanup code + +- fastcgi + + remove double-free + added handling of EINTR in some places + +- leaks + + fixed some leaks in the new config code + +- array_strcasecmp + + fixed alignment in the improved array_strcasecmp function (adam) + +20.08.2004 14:46 - pre-release +- performance + + optimized a few useless strlen() away as we either know the length from + buffer->used - 1 or by sizeof(str) - 1 if it is constant. + + optimized the 'find the \r\n\r\n' function. + + improved the array_strcasecmp() based on another idea from (ralf) + +- accesslog + + enabled the strftime cache again + +15.08.2004 23:41 +- accesslog + + added apache-like CustomLog handling in accesslog.format + + accesslog.format = "..." + +15.08.2004 21:08 +- test-cases + + remove testdir + +- configfiles + + handle escaping of " in strings properly + +13.08.2004 12:07 +- array + + improved inner-loop of array_strcasecmp() (ralf) + +11.08.2004 14:14 +- fcgi socket + + use SUN_LEN if available + +- keep-alive + + disable keep-alive on request + + server.use-keep-alive = "disable" + +10.08.2004 15:59 - 1.2.5 +- conditional config + + mod_fastcgi + mod_rewrite + mod_redirect + mod_access + mod_compress + mod_accesslog + + are ported + +10.08.2004 13:05 +- pipelining + + fixed very stupid pipelining bug + +09.08.2004 22:07 - pre-release +- conditional config + + first code for conditional config + +09.08.2004 14:21 +- fcgi + + fixed access to free()'d memory (doesn't create any harm) + +- isdigit, warings + + signed -> unsigned for 2 more isdigit() calls (adam) + removed some unused var's if pcre is not available (adam) + +08.08.2004 20:57 - pre-release +- debian + + added a chmod to /var/log/lighttpd/ (allan) + +08.08.2004 12:05 +- kqueue + + use EV_SET() instead of setting the ev-struct by hand (adam) + +- fcgi + + fixed the EINPROGRESS handling to use getsockopt (er) + fixed a leak of server is disabled (er) + +- solaris 10 port-api + + added a skeleton for the sol10 port api + +06.08.2004 10:18 +- mod_ssi + + fix DATE_LOCAL so it displays the correct time zone (Jeremy Hinegardner) + +04.08.2004 11:43 +- openbsd fixes + + dropped usage of MAX() in buffer.c + added prober includes for md5.h if openssl is enabled (brad) + +- memory usage + + documented the way how lighttpd caches memory blocks + reset the buffers after they have been written by the network-layer + +- kqueue + + modify fd-bitmask only if kevent succeeded (adam) + + +03.08.2004 15:09 +- mod_compress + + compress even if you have no cachedir set + +03.08.2004 13:26 - pre-release +- Makefile + + fixed dependencies for parallel build in mod_ssi_expr.c + +- combo patch + + * Tinker with kqueue(). Add a reset method so that the kqueue file + descriptor can be re-enabled after a fork(). Emulate the devpoll driver + in that adds and deletes are sent to the notification mechanism im- + mediately, which should cut down on phantom events. Use + ev->kq_results as a sliding window. + + * Change F_SETFD calls to use the preferred FD_CLOEXEC instead of 1. + + * Remove unnecessary fdevent fcntl handlers. It appears that the only + driver that needs one is Linux RT signals. + + * Quiet compiler warning about unused parameter. + + * Set the close-on-exec flag for the /dev/poll and epoll_create() file + descriptors. + + * Return failure if /dev/poll could not be opened instead of logging + and continuing. + + * Detect EAGAIN after writev() failures. FreeBSD sendfile() doesn't need + protection, as the man page says: + + When using a socket marked for non-blocking I/O, sendfile() may send + fewer bytes than requested. In this case, the number of bytes success- + fully written is returned in *sbytes (if specified), and the error + EAGAIN is returned. + + (adam, georg, matt) + + +02.08.2004 18:08 +- mod_ssi + + check for pcre before compiling the module + +- fdevents + + dropped fdevent_fcntl added by the last patch (adam) + kqueue: events == FDEVENT_IN -> events & FDEVENT_IN (adam) + +31.07.2004 22:07 - 1.2.4 +- fdevents + + * Test at configure time for kqueue() and <sys/event.h> + * Remove various hard-coded constants from event handlers + * Move maxfds into the event structure, and out of the + fdevent_*_init handlers. Event handlers can use the maxfds + member to size arrays. + * Various event structure renames to discourage clashes + * Remove extra (ignored) call to fdevent_event_next_fdndx() in + the main server loop. + * Wrestle with kqueue(). The implementation has to deal with + phantom events (for fds which have been deleted/closed), similar + to the Linux RT signals code. Like the RT code, it maintains a + bitmask of active fds. After a successful call to kevent(), the + code will compress/overwrite dead events. The other annoyance is + that the handler must track the event filter for each fd, even + though you cannot support both read and write filters for the same + fd in one kqueue. The handler maintains a separate bitmask for fd + filters (1 == EVFILT_IN, 0 == EVFILT_OUT). + (adam) + +- server side includes + + added native server-side includes based on the docs from apache: + http://httpd.apache.org/docs/mod/mod_include.html + + not supported are: + - exec + - nested virtual + - config.errmsg + - echo.encoding + +24.07.2004 +- fdevents + + added a bitset to figure out if we received a event for an unregistered fd + in rt-signal (adam) + +- kqueue + + added kqueue support (Matt Levine) + +13.07.2004 08:58 +- configfile + + parse keys correctly that contain a digit (Geoff Adams) + +- fcgi + + fixed large post uploads (Geoff Adams) + fixed uri if docroot is set (Geoff Adams) + +03.07.2004 22:50 - 1.2.3 +- index-files + + rewrite uri.path to the index-file instead of keeping it at .../ + this fixes index-file handling in FastCGI/CGI docroot is used + +- close-on-exec + + enable close-on-exec handling to simplify FD handling in CGI code + +- cgi + + keep error-log-fd open to catch the error handling for execve() + + report error if cgi-exec file doesn't exist + +- proxy + + pass page-content on error to the user (E.R.) + code cleanup (E.R.) + +- ssi + + first skeleton of a plugin for ServerSideIncludes + +- security + + limit the headerlength again to 64k max + +03.07.2004 14:23 +- configure + + fixed compile-check for libpcre if pcre-config doesn't point to /usr/lib + +02.07.2004 18:17 +- buffers + + always allocate a multiply of 64bytes. this should reduce the number of + realloc()s and still doesn't has a too high overhead. + +02.07.2004 11:07 +- fds + + connect stdin, stdout and stderr to /dev/null instead of just closing it + use dup2() instead of dup() + +- accesslog + + if accesslog.filename starts with a | spawn a process which will get data + in one chunk once in a while + +01.07.2004 11:00 +- sample config + + added text/css and text/javascript mimetypes + +28.06.2004 12:18 +- proxy module + + added a proxy module (based on the fastcgi module) and added some + documentation + +25.06.2004 21:41 +- last-modified handling + + replaced %Z by GMT. otherwise the last-modified check will most often fail. + +24.06.2004 20:20 +- relax http-parser + + don't reply 400 in case of an empty header-field, just ignore it + +23.06.2004 22:10 +- file-cache + + don't cache mmap() for files larger than 64k as we run out of RAM otherwise + too fast (check with some 200mb files) + +- 64bit fixes + + fixed buffer_equal on sparc64 + +15.06.2004 19:09 - 1.2.2 +- mmap cache + + fixed mmap-caching in network_write.c and network_writev.c after a direct + hint by E.Rodichev + +- sendfile + linux + + check at config-time if sendfile() works on Linux + +11.06.2004 15:09 +- fcgi + unix sockets + + added support for unix domain sockets (spawn-fcgi 1.2.0 required) + +10.06.2004 11:49 +- configure + + use pcre-config to determine the position of the pcre headers + +05.06.2004 22:06 +- filehandle-cache + + remove mmap-segment if filecache gets invalidated + +30.05.2004 14:13 - lighttpd 1.2.1 +- response headers + + request headers that appear twice are grouped together like expected by + the CGI spec (concat with a ", ") + response headers behaved the same way but are not grouped anymore. They + stay seperated. Actually they are concated by \r\n<key>: <value> which is + the same in the end. + +- file uploads + + the handling of longer post requests is fixed now. + +28.05.2004 09:13 +- cgi + + added support for direct calls to cgi-binary + +22.05.2004 21:58 +- pipeling + + the code cleanup is finished successfully. Now all cases of pipelining are + handle the right way. POST pipelining was still not working up to now. + +22.05.2004 12:55 +- code cleanup + + use the well-tested code from the write-queue as the base for the + read-queues and simplify the pipeline handling alot that way. + +20.05.2004 15:08 +- network backends + + enabled sendfile support again (__FreeBSD__ instead of __freebsd__) + + added a mmap cache as part of the filedescriptor cache + + added AIX specific send_file() support (untested) + +20.05.2004 10:40 +- segfaults + + fixed some minor segfaults on startup when no config file is used. + +17.05.2004 10:58 - lighttpd 1.2.0 +- documentation + + reformated the documentation the doc/ directory + +15.05.2004 14:45 +- localizer + + fixed build of localizer extension + +15.05.2004 12:35 +- POST requests + + there is no need to die if we spot a simple POST request on a static file + +- pipelining + + fixed HTTP/1.1 pipelining which caused the problems with opera + +- array handling + + how did that bug survive such a long time ? a wrong compare function was + used in one case, but not the other. + +15.05.2004 03:20 +- secure and fast downloads + + added a module which allows secure and fast downloading of files: + 1. the application (.php, ...) controls the access to the files + 2. the webserver is handling the transfer (and check the app generated + tokens) + + the token is based on + - a secret + - a timestamp + - the filename + this means even if the token is is distributed by the user it will get + invalid after a given timeout (default 60 seconds) + +- errorfiles + + check for errorfiles before using them + +- code cleanup + + applied cleanup fixes from adam + +14.05.2004 18:47 +- fdevent handling + + added a more generic callback interface to the fdevent structures which + simplifies the writing of plugins. this might destabilize lighttpd for a + while + +- cgi + + fixed header parsing of the header is sent in chunks and the terminator is + sent in a single chunk + +- EINTR + + fixed some occurences of EINTR which read() + +03.05.2004 23:55 +- portability + + E.R.: + * portability fixes for Solaris 2.5 + +02.05.2004 10:15 +- Expect: Handling + + added incomplete support for Expect: 100-continue (RFC 2616 8.2.3) by + sending always 417 for every Expect-request (see 14.20) + + we have been blamed for not supporting it: + http://lists.w3.org/Archives/Public/ietf-http-wg/2004JanMar/0059.html + +29.04.2004 23:07 - 1.1.9 +- usertracking + + added a very basic usertracking cookie handler + +29.04.2004 19:37 +- network-writev + + Adam: + * call munmap() on error in write() + +- docs + + E.R: + * documented 'check-local' + +- test-env + + made the shell scripts more portable (checked with zsh, bash and ksh) + + fixed compilation on NetBSD + +28.04.2004 22:22 +- FastCGI + + E.Rodichev: + * added "Authorizer" mode for FastCGI + +27.04.2004 18:08 +- ssl + + Alexey Slynko: + * handle SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE in SSL_write the + right way. + +- FastCGI + + add 'check-local' instead of the implicit 'if-docroot-is-set' handling + implemented at 24.04.2004 14:34 + + E.Rodichev: + * remove useless extra-/ in before uri.path + * add 'prefix' notation for FastCGI processes + +26.04.2004 16:52 +- code cleanup + + patches from Alexey Slynko: + * remove the pidfile if lighttpd terminates the normal way (if not in chroot) + * init SSL before getting daemonized + +25.04.2004 19.15 +- mem-leak + + fixed mem-leak on broken HTTP-headers + +- FastCGI + + patches from E.Rodichev: + + 1. CONTENT_LENGTH + "If no data are attached, then this metavariable is either NULL or not + defined". + + 2. QUERY_STRING + If the Script-URI does not include a query component, the QUERY_STRING + metavariable MUST be defined as an empty string (""). + + 3. Doubling of REMOTE_ADDR removed. + + patches from adam: + * fixed bug in the sizeof() patches from yesterday. + * some more *_long to *_off_t + +24.04.2004 14:34 +- FastCGI + + don't check for localfile if 'docroot' for a FastCGI host is specified. + +24.04.2004 12:13 +- POST + + fixed POST request handling + +- chunk-encoding + + the generated HEX strings where broken since the 1.1.8 + (this effected all HTTP/1.1 requests without Content-Length like FastCGI-PHP) + +- code cleanup + + patches from adam: + * malloc + memset -> calloc + * sizeof(int) -> sizeof(<variable>) + * assign fd_set instead of memcpy() + * init fd -> connection fd pointers to -1 + +16.04.2004 08:48 - 1.1.8 +- code cleanup + + don't reuse buffer > 64k (see settings.h) + + added server.max-request-size to limit the maximum request-body size + (in kBytes) + + don't accept HTTP-request headers larger then 32kBytes (see settings.h) + + minor speed improvements in the request-parser + + More cleanup patches from adam: + + * change pre-ANSI C/valid C++ syntax for function declarations/definitions + from using () to (void). Ex: int foo(); --> int foo(void); + * use static linkage as much as possible, to limit possible symbol + collisions + * whack more unneeded variables + * try and prevent any errno clobbering by storing the old errno value before + any subsequent system calls, and restoring before function exit. + * change printf syntax for unsigned variables from %d to %u + +15.04.2004 18:41 +- code cleanup + + handle all int != size_t cases in fcgi.c correctly + + check headerfields to have a value + + handle both EINVAL cases of writev() before the can occur + + limit content-length to SSIZE_MAX + disallow negative content-length + + the usage of ltostr() has been reduced to the minimum in favour of + buffer_/append|copy)_(long|off_t) + + dropped ultostr() and ultohex() in favour of buffer_* + +15.04.2004 16:35 +- portablity + + more patches from adam: + + * remove warnings for unused parameters and variables + * remove warnings for mismatched pointer assignments + * change "gtime_r" to "gmtime_r" + +13.04.2004 20:48 +- test-cases + + made the 'make check' target self-contained + + In our case we have to call the targets in the following order: + + $ ./configure ... + $ make + $ make install + $ make check + + because the path to the plugins is hardcoded in the binary itself + + using + + $ ./configure --prefix=/tmp/lighttpd-1.1.x/ + ... + + will help if you don't really want to install before testing. + +13.04.2004 00:05 +- portability + + adam sent another patchset: + + * Wrap PCRE-specific data member access with an #ifdef + * Add const to pointer using return value from dlerror() + * Explicitly initialize pointer in the lemon parser to 0, + in order to catch missing else { } clause + * Use a time_t rather than an int for gmtime() call. On some + systems (including 32- and 64-bit SPARC) time_t is a long. + +12.04.2004 17:00 - 1.1.7 +- fastcgi + + strip WS after HTTP-response headers coming from the FastCGI process + + added REMOTE_USER to the Server->FastCGI headers + removed HTTP_AUTHORIZATION from the Server->FastCGI headers + +12.04.2004 10:24 +- cgi + + if we don't get a partial HTTP-response-header send the content out as soon + as the cgi script is finished + +12.04.2004 01:23 +- compression + + added bzip2 compression (supported by w3m) + +12.04.2004 00:12 +- configfile + + add some usefull error messages if the tokenizer or the parser fail to + read the configfile + +11.04.2004 22:04 +- configure + + added --with-ldap and --disable-lfs to the configure options + +11.04.2004 20:28 +- 64bit offset size + + disable linux-sendfile support for linux 2.4.x for now as it don't + support 64bit transfers + + fixed all assignments on the path from the stat() to the Content-Length + HTTP-header + +- head requests + set content-length in HEAD requests + +- accesslog + + write accesslog entry on network error + + write the correct amount of byte written to the accesslog + +11.04.2004 11:48 +- code cleanup + + moved the config for the cgi-plugin from config.c to the plugin. + + moved some buffers which were only used by a one or two plugin from + the server-structure to the plugins + + keeping the plugins independent from the server-core is a 'good thing' + +10.04.2004 19:06 +- configfile parser + + removed the leaks from the configfile parser + +09.04.2004 23:15 - 1.1.6 +- stricter http-parser + + added line-folding although noone really seems to use it. + +09.04.2004 18:42 +- configfile parser + + the hand-written configfile parser has been replaced by a LALR(1) one. + 'lemon' from the sqlite guys has been used to generate the parser. + +- by-host, by-url, by-file, by ... + + $HTTP["url"] =~ "~$" { + access.deny = "all" + } + + $HTTP["host"] =~ "." { + simple-vhost.server-root = "/home/weigon/wwwroot/servers/" + simple-vhost.default-host = "grisu.home.kneschke.de" + simple-vhost.document-root = "pages" + } + + $HTTP["host"] == "incremental.home.kneschke.de" { + server.docroot = "/hasdasd" + } + + at least the parser can handle it now. Currently there is no real support + for this context-based config-option. But the syntax and the parser are + done. + +09.04.2004 10:58 +- ssl support + + enable ssl support again + +- mmap + + enabled mmap+write again + +08.04.2004 12:34 +- stricter http-parser + + based on a thread at + + http://lists.w3.org/Archives/Public/ietf-http-wg/2004JanMar/0050.html + + the HTTP-parser has been adjusted to be more correct when it comes to + request-header fieldnames + + the whitespace handling around the ':' has been relaxed as requested + +07.04.2004 17:06 +- sigaction + + use sigaction instead of signal if possible + +07.04.2004 13:55 +- accesslog + + use localtime-timestamps in accesslogs if struct tm has a tm_gmtoff field + +07.04.2004 10:41 - 1.1.5 +- -D_REENTRANT + + solaris + localtime_r() needs it + +07.04.2004 02:54 +- mod_auth + ldap + + added a ldap backend to the auth plugin. + +06.04.2004 13:37 +- pidfile + + fixed the permissions of the pidfile (Matthijs van der Klip) + +- specfile + + merge the RedHat and SuSE specfile with Matthijs + +- 64bit file-offsets + + moved the FILE_OFFSET_BITS settings from the config.h the Makefile to enable + 64bit offsets the right way + +06.04.2004 12:32 +- mod_expire + + added an apache compatible mod_expire which adds Expires: headers to the + request + + expire.url = ( "/buggy/" => "access 2 hours" ) + +05.04.2004 22:34 +- solaris devpoll + + forgot to provide the infrastructure to actually enable the devpoll event + handler. the same has been done for the freebsd-kqueue handler (which + doesn't work yet) + + fixed the devpoll support while testing it on a real solaris box + +05.04.2004 09:49 +- debian + + added debian packaging support written by Vincent Wagelaar + +- solaris + + Another set of patches for Solaris from Adam + * Detect <sys/devpoll.h> + * Detect and include <sys/filio.h> for definition of FIONREAD + * Detect and link against the library exporting hstrerror + * Correct typo in SENDFILE_LIB + * Use__sun instead of __solaris__ for detecting operating system. + Please see http://predef.sourceforge.net/preos.html for more + detail + * Explicitly cast arguments to isalpha() and toupper() to unsigned + char. The man page says that the functions support the range of + an unsigned char, and EOF. + * Include <limits.h> and define UIO_MAXIOV as IOV_MAX for Solaris. + +04.04.2004 18:05 - 1.1.4 +- pidfile + + added pidfile writing after deamonizing + +04.04.2004 01:05 +- fdevent + + added framework for freebsd_kqueue and solaris_devpoll + + the solaris_devpoll one might event work (untested) + +03.04.2004 16:41 +- network + + added framework for filebased chunks + - read-write + mmap-write + - linux-sendfile + - freebsd-sendfile + - solaris-sendfilev (untested) + + and memorybased chunks + - write + - writev + + made TCP_CORK a 'global' flag around the write_chunkqueue-calls + + the writev() support should improve the performance for all non-static + pages. + + 170 req/s against 158 req/s for the following script if writev() is used + instead of write() + + <?php + + for ($i = 0; $i < 1000; $i++) { + print $i."<br />\n"; + flush(); + } + + ?> + +28.03.2004 13:42 +- cleanup + + applied some cleanup patches submitted by Adam: + * variables modified in signal handlers should be sig_atomic_t + * assert statements should not have side effects + * STD{IN,OUT,ERR}_FILENO preferred instead of {0,1,2} + * dieing --> dying + * SEGFAULT calls abort directly, instead of derefencing a NULL pointer + +- mod_accesslog + + modified the accesslog format to be CLF compatible + set locale for LC_TIME to C + +26.03.2004 16:13 +- path info + + enabled the pathinfo code again + + +25.03.2004 13:30 - 1.1.3 +- portability + + compiles fine now without any patches on IRIX + +- hostname detection + + reworked "get the hostname for HTTP/1.0 requests which don't specify a + Host: ..." to only query the name in this single case for the server side + of the connection + +- errorlog handling + + stderr is only used until the errorlog is init'ed + if no error-log is specified, syslog() is used + if cycling error-log fails, syslog() is used + +- accesslog cycling + + don't fall back to stdout anymore + +- event-handler + + use poll() as the default event-handler again + +24.03.2004 01:37 - 1.1.2a +- error messages + + added some hints to the failing error-messages + +22.03.2004 01:58 - 1.1.2 +- configure + + some protability changes to get the 'inline' working with the MIPS CC + +21.03.2004 22:00 +- mod_rewrite, mod_redirect + + reading the config for those two plugins was not working + +- fdevents + + changed the compile time setting for the event handling into a run-time + setting. + + server.event-handler = "select" # poll, linux-rtsig, linux-sysepoll + + added sys_epoll() for linux 2.6 + + select - all systems + poll - Linux 2.1.23+, all XPG4-UNIX + sigio - linux 2.4.0+ + sysepoll - linux 2.5.66+ + + 1000-4k-nok 1000-100k-nok + select 1776.99 296.52 + poll 678.02 607.28 + sigio 3754.46 1411.23 + sysepoll 3817.67 1431.02 + +21.03.2004 00:10 +- configure script + + rewrote large parts of the lib/header detection of the configure script + +20.03.2004 01:39 +- fastcgi + + as the docroot on external hosts might be different than the webserver + docroot it can now be specified in the config: + + fastcgi.server = ( ".php" => + ( "grisu" => + ( + "host" => "192.168.2.41", + "docroot" => "/home/jan/servers/", + "port" => 1026 + ) + ) + ) + + a huge internal cleanup in the config handling made the code more readable. + some more warnings and error checking should track most of the config + errors for the fastcgi plugin + +19.03.2004 12:34 +- external patches + + Matthijs van der Klip submitted three nice patches: + - turn off writing in mod_status if status.rrd-reports is set to disable + - fix for a debug message + - get the hostname for HTTP/1.0 requests which don't specify a Host: ... + - rc-script for RedHat + +- documentation + + added a documentation section about authentification + - doc/authentification.txt + +19.03.2004 05:11 +- optimizations + + implemented special versions of + - strcasecmp (array_strcasecmp) + - isdigit, isalpha, isalnum (light_*) + + added a faster check for a finished header + + after disabling all modules it is still possible to get 20 kreq/s. + +15.03.2004 19:45 - 1.1.1 +- localizer server + + added the localizer-server module to the code + +- chunked-encoding + + Apple's Safari or HTTP-Handler doesn't handle chunked-extensions defined by + RFC 2616 correctly and doesn't ignore them. Disabled the chunked-extension + which were just used for debugging purposes. + +07.03.2004 12:20 +- optimization + + moved the checks if a plugins support a given function from the dispatcher + (plugins_call) to plugins_call_init() to do the check only once. + + equalized the plugins functions to only two types: + SERVER_FUNC() + CONNECTION_FUNC() + + replaced all handwritten plugin_call-handlers with macros + + made all plugin-functions 'static' + + with all plugins loaded we are at 15kreq/s gain + without any plugin loaded at 16kreq/s + + before the change we were at 13kreq/s + +06.03.2004 21:13 +- compilation fixes + + fixed some warnings on FreeBSD and NetBSD by adding + #include <netinet/in.h> + + ifdef'ed a pcre-entry in base.h + + remove #define _XOPEN_SOURCE from http_auth.c for IRIX + crypt() on Linux needs _XOPEN_SOURCE + +06.03.2004 19:18 - 1.1.0 +- authentification + + added htpasswd and htdigest backends to work against user-files generated + by htpasswd and htdigest. + + for basic auth: + - plain + - htpasswd (crypt only) + - htdigest + + for digest auth: + - plain + - htdigest + +06.03.2004 17:35 +- authentification + + check the method in the authorization header againt the configured method + +06.03.2004 14:54 +- hostname parsing + + added a RFC-2396 conforming "Host:" parser + added 17 checks for the parser + +06.03.2004 10:25 +- configuration + + added a warning for unknown configuration variables. + + dropped the 'specific-for.url' ideas for now as it is not known yet how to + implement it the right way + + renamed some config-variables to reflect there actual meaning + - server.host -> server.bind + - server.virtual-* -> simple-vhost.* + - server.userid -> server.username + - server.groupid -> server.groupname + - server.docroot -> server.document-root + + +05.03.2004 15:36 +- fastcgi + + internals: moved all mod_fastcgi settings from the global struct to the + plugin itself + +- vhosting + + got a patch for enhanced virtualhosting from christian kruse -> mod_evhost + + moved the virtual hosting config (server.virtual-*) to it's own module + called mod_simple_vhost + +23.02.2004 10:06 +- configfile + + rearragned the config-file structure again to be able to add settings for + a given URL, Host, Filename, ... + This change affects some config-options directly (access.deny, + url.rewrite, url.redirect, virtual-hosting, ...) + + added 'specific-for.url' for url specific config settings + +- digest auth + + FIX: md5-sess type + + seperate the auth-backend stuff + +13.02.2004 22:23 - lighttpd 1.0.3 +- content-length + POST + + FIX: If header and content didn't fit into one single packet the rest of + the content was not read correctly + +13.02.2004 01:07 +- content-length + POST + + the check for content-length on a POST request vanished somehow in one of + the previous releases. + +- header search + + FIX: the search for header fields was up to now case-sensitive. Now it is + like required by the standard case-in-sensitive. + +- browser bugs + + w3m 0.2.5 adds an additional \r\n at the end of the POST requests which is + handled now + +10.02.2004 10:12 +- start script + + took the suse rc-skeleton which states to be LSB compliant and modified it + for lighttpd needs + +09.02.2004 11:48 +- HEAD requests + + FIX: HEAD requests for static files delived the content from the GET + request. (test case is added) + +08.02.2004 15:53 +- directory listings + + FIX: the pathname has not encoded at all before it was transfered to the + browser. a proper url and html-encoding has been added. + + added modification-time and filesize to the output + + added a basic css for the virtual listings + +07.02.2004 22:15 - lighttpd 1.0.2 +- sample configfile + + rearranged the config-file to have all the important options at the top + +- docs + + added a mod-rewrite options + +- mod_accesslog + + stdout is no longer used a default for the accesslog + +- error-messages + + the 404 is now reported in the error-log + +07.02.2004 17:30 +- configfile handling + + if a key is used twice like + + url.rewrite = ( "url1" => "url") + url.rewrite = ( "url2" => "url") + + you get an error now. You have to write: + + url.rewrite = ( "url1" => "url", + "url2" => "url") + +31.01.2004 12:22 - lighttpd 1.0.1 +- log cycling + + added a sighup-handler to the plugin interface and fixed the log-cycling + for access-logs + +- portability + + disabled the interval-time optimization on IRIX + +- documentation + + added a lot of new documentation to README + +31.01.2004 10:59 +- status module + + added two new options rrd-dir and rrd-reports: + + rrd-reports = (boolean) # enables RRD-reports + rrd-dir = (string) # path for the daily status-files + + fixed the "status-files in /"-bug this way + + +22.01.2004 13:38 - lighttpd 1.0.0 +- simple docroot + + fixed handling of docroots if virtual-host is disabled + +27.12.2003 11:12 +- license handling + + added the first interface to license handling. + +25.12.2003 23:48 +- protability + + Verified again that the code compiles and runs cleanly on Linux, FreeBSD, + NetBSD and IRIX + + compiling with gcc and the option -pedantic works fine + compiling with mipspro cc works fine, too + +- tests + + added some more tests: 39 tests + +25.12.2003 16:01 +- protability + + some compile fixes for FreeBSD have been applied and a new switch has been + added to choose between IPv4 and IPv6 on FreeBSD. + (cmdline: -6, configfile: server.use-ipv6) + +- packaging + + cleaned up the specfile for building RPMs + +21.12.2003 01:00 +- authorization + + brought basic and digest auth back to life. this module as the last one + which had to be updated after the config-file changes + +- test harness framework + + add 3 tests for basic auth + +20.12.2003 22:10 +- compression + + added gzip compression (gzip-header + deflate + crc) + +- test harness framework + + added a 22 tests to verify the correct behaviour of lighttpd + +- request parsing + + GET http://www.yahoo.com/ HTTP/1.0 + + is handled now as + + GET / HTTP/1.0 + +- plugins + + moved FastCGI and CGI handling into modules which can be loaded at run-time + +17.12.2003 13:18 +- compression + + the directory structure is now build automaticly + +15.12.2003 01:00 +- compression + + added a compression cache to the compression module (mod_compress) + + Vary: Accept-Encoding is set now as it influences to delivered stream of + bytes. This is important for caches. + +10.12.2003 00:24 +- config files + + a new config-file format is ready for the final release of lighttpd. + + it supports: + + server.docroot = "string" + server.host = integer + server.modules = ( "string", "string" ) + server.mimetypes = ( "key" => "value" ) + server.complex = ( "key" => ( "string", integer ), + "string", + integer ) + + the syntax should look familar to all who worked with Perl or PHP. + + config-file handling has been seperated into a parser and a lexer. Both + are currently written by hand and will propably be rewritten into bison + + flex if time permits. But that would be a cosmetical change. + +05.12.2003 03:07 +- status-page + + improved the status page to display + - the current connection-stati + - average throughput over 5 seconds + (requests/s and output-bound traffic) + + now you can see what is going on in the server + +- access-log + + the accesslog is now a module and can be disabled by just removing the + plugin from the list of loaded plugins + +04.12.2003 16:18 +- chroot-ing + + how-to: using chroot + + - chroot to /home/www/ + - docroot at /servers/<hostname>/pages/ + - defaulthost www.example.org + + e.g. (external view) + /home/www/servers/www.example.org/pages/index.html + + (in chroot) + /servers/www.example.org/pages/index.html + + config: + + chroot /home/www/ + userid wwwrun + groupid nogroup + + virtual-server-root /servers/ + virtual-server-default-host www.example.org + virtual-server-docroot /pages/ + + The FastCGI process is living outside this chroot definition as it is + started seperatly. + +01.12.2003 02:06 +- cleanup + + in preparation for the first stable release some internals had to be + cleaned up. Basicly it was a cleanup of workflow of the creation of the + response-header. All modules can use a clean interface for this purpose + now. This is espacially usefull for all modules which have to pass some + HTTP-headers to the client. + +29.11.2003 22:22 +- modules + + finally moved the modules to shared libraries and cleaned up some code + path to become more readable. + + So far we have: + + mod_rewrite + mod_redirect + mod_access + mod_auth + mod_cache + mod_chat + mod_status + mod_maps + +28.11.2003 18:16 +- redirects + + as lighttpd supports url-rewriting redirection was a few lines of new code. + + redirect ^/wishlist/(.+) http://jan.kneschke.de/wishlist/$1 + + rewrite ^/wishlist/(.+) /new/wishlist/$1 + +28.11.2003 17:00 +- signal stuff + + setitimer is used to send a event every second to call time() only once a + second. another system call which has been remove from the main-loop. + + sending HUP to lighttpd will close and re-open the logfiles. this is used + for cycling logfiles. + +#! /bin/sh + +### +# +# a simple logfile rotator for lighttpd +# + +DATE=`date +"%Y%m%d-%H%M"` +LPID=`pidof lighttpd` +mv access.log access.log.${DATE} +kill -HUP ${LPID} +gzip access.log.${DATE} + +27.11.2003 01:07 +- native win32 port + + a first attempt for a native win32 has been done. For now mingw is the + base for the development as it provides a basic unix-like framework for + building native win32 applications. + + the most internal files have been ported and the over all progress is + going well. At the end this will just be a prove of concept. + +26.11.2003 01:17 +- access denied + + added a access-deny filter to block specific urls like + + access-deny ~ + access-deny .inc + +17.11.2003 01:06 +- bug fixing + + a fstat() on a opened fd which has changed reports wrong the file-info. + Using stat() again helps to solve this problem. + + the sig-io version doesn't suffer from this problem. + + the etags are now used for verifing file-cache-entries. + +- chat + + enabled the internal login mechanism again + + added support transfering session-infos over MySQL. + +15.11.2003 00:19 +- optimizing + + added a buffer_equal_reverse function which is optimized version of strcmp + which is going backwards as pathnames are often the same for in the first + bytes. + + wrote a one-pass parser for the request-header. The combination of + strstr(..., "\r\n") to seperate lines and strchr(..., ':') to seperate + keys from values more or less touched every byte twice. + + we are still at 18.000 req/s for 4kb keep-alive requests even with etags + and handling all header fields. + +14.11.2003 17:26 +- fcgi + + Content-Type wasn't passed correctly to the FastCGI app. HTTP_CONTENT_TYPE + was sent instead. + +- cache + + provided access to the Session-ID + +- error-log + + the timestamp is now written in a human readable form + +22.10.2003 00:06 +- fcgi, cgi + + added a special set of array-functions which are optimised for the "insert + only once" case. + + this provides access to the headers which are now forwarded to the external + interfaces. Before this change only a limited set of request-headers were + forwarded. + +21.10.2003 11:58 +- modules + + added a new module-hook after the basic-init of the module for handling + config-settings and prepare the overall operartion (like building + db-connections, compiling regexes, ...) + +- cache + + use turckmm-cache 2.4.3 to get some numbers for the php-latency: + + /usr/sbin/ab -n 10000 -c 10 http://alba.home.kneschke.de:1025/index.php + + handling the cache-decision and the cache-hit in php: + + cache-miss: 100% (-> $version = 0) + + plain : 108.13 req/s + turckmm-cache: 218.39 req/s + + cache-hit: 100% (-> $version = 1) + + plain : 164.45 req/s + turckmm-cache: 653.98 req/s + + handling the cache-decision and the cache-hit in the server: + + cache-hit: 100%, but using index.cml + + cml: 4918.84 req/s (no keep-alive) + cml: 6901.31 req/s (keep-alive) + + cache-miss: 100%, but using index.cml + + plain : 108.39 req/s + turckmm-cache: 217.84 req/s + + Conclusion: + - there is no loss in the cache-miss case through the cml-handling + - the cache-hit case can be improved dramaticly with lighttpd-cache + - turckmm-cache improves the cache-miss case alot + +20.10.2003 00:40 +- cache + + the first 'real-life' test showed dramatic improvements in the req/s + handling. + + The basic idea was to move the decision if a php-page can be taken from + the cache from the php-code to the webserver. + + See here why this is a good thing: + + the quite common code which works for http://jan.kneschke.de/ is using + templates and is quite static, but depends on 4 external files (the + menu-structure, the template, the current content, the class-file). + + the index-file is always: + + include_once "jk.inc"; + + $v = new view(); + print $v->get(array(array ("file" => "content.html"))); + + It is more or less the same for all pages. + + This basic setup can deliver 100 requests/s. + + The next step has: + - application bases caching + + as we know that each pages depends on those 4 files, you can check if they + have been modified since the last request and deliver the content from the + cache otherwise. + + this increased the throughput to 150 req/s. (cache-hit ratio 100%) + + The next logic step is to move the decision-making process out of the PHP + code as PHP is to slow for the cache-hit path: + + a CML (Cache-Markup-Language) has been written which describes the whole + decision process which has been written in PHP-code before: + + +output.content-type text/html + +output.include _cache.html + +trigger.handler index.php + +trigger.if file.mtime("../lib/php/menu.csv") > file.mtime("_cache.html") +trigger.if file.mtime("templates/jk.tmpl") > file.mtime("_cache.html") +trigger.if file.mtime("content.html") > file.mtime("_cache.html") + + if one of the 'trigger.if' statements is true the 'trigger.handler' is + called to generate the pages. + + if none of the them is trigger the files from 'output.include' are sent to + the browser with content-type specified in the first line: + + The result was very 'promissing': + + 5900 req/s with keep-alive + 3800 req/s without keep-alive + + (both for a cache-hit ratio of 100%) + + for keep-alive this is factor <b>59</b> against the plain un-cached + version and still <b>39</b> againt the php-cache-version which is doing + exactly the same. + + Time for party. :) + +19.10.2003 - 18:55 +- cache + + the handling of functions has been improved. they are now 'plugable'. just + the dlopen() stuff is missing. + + a new datatype has been added: the string + + this makes it possible to evaluate something like: + +trigger.if unix.time.now() - file.mtime("head.html") > 30 +trigger.if mysql.query("SELECT count(*) " + \ + " FROM structure AS struc, session AS sess " + \ + " WHERE struct.user = sess.user" + \ + " AND sess.id = \"" + mysql.escape("ab\"c") + "\"") + + you see: + - string operations (concat) + - handing of escape-sequences + - functions + - comparisions + +18.10.2003 - 13:39 +- cvs + + imported everything into the cvs server which makes the whole revision + handler a lot easier. The CVS server was up all the time but not used. + Importing required 5 minutes of work which included merge the freebsd and + the linux tree. + +- compilers + platforms + + on a regular various compilers and platform are check to compile with with + the current code base: + + platform | os | compiler | state + ---------+---------------------+--------------+--------- + ia32 | Linux 2.4.22 | gcc 2.95.3 | ok + ia32 | FreeBSD 5.1-CURRENT | gcc 3.3.1 | ok + mips64 | IRIX 6.5 | gcc 3.2.2 | ok + misp64 | IRIX 6.5 | MIPSpro 7.41 | ok + + the mipspro compiler revealed some warning which resulted in a nice + code-cleanup that made the code more readable. + +18.10.2003 - 03:00 +- e-tags and friends + + in preparation for the php-conference at the begin of november in + frankfurt/main the server has the support some more caching/proxy tags + like: + - E-Tag (14.19) [done] + - If-Match (14.24) + - If-None-Match (14.26) [done] + - If-Range (14.27) + + Section (13.3.3) binds them together. [RFC 2616] + + using lxr.kde.org and lxr.mozilla.org revealed that + - konqui only uses if-none-match + - mozilla uses if-none-match and if-range + + it looks like it isn't that easy to trigger the if-range case. + + As ulf suggested the etag is a hash of file-size, inode-number and mtime. + +- fd-caching + + ulf just phoned me ask proposed the free the cache more agressivly in case + of fd-shortage. increasing the the fd-limit is the better idea. :) + +17.10.2003 12:45 +- chat + + finished the mysql-support for storing the sessions + +12.10.2003 20:56 +- valgrind + + used valgrind again to verify that the code a free of mem-leaks and found + a 'leak generator' in the chunk-api. + + the last few leaks were just some missing free()'s at the end of the + program run which would have been freed anyway. + + at the end valgrind couldn't find any missing free()'s. + +11.10.2003 12:09 +- FastCGI + + reduced the number of system calls for FastCGI to WebServer to 2 calls per + fd-event. (ioctl() + read()) + + this has no direct effect of the performance of the server, but improves + the possible througput of the load-balancer. + +10.10.2003 21:09 +- FastCGI - load-balancing + + a brown paper bug has been fixed which caused to decreasing throughput if + load-balancing was enabled. + + + benchmarking the req/s with load-balancing shows really nice results: + + + server : req/s comment + ---------+-------------------------------------------------- + ulf : 764.06 (php) + lappi : 800.06 (php) + + ulf+lappi: 1526.95 (2 * php) + + grisu : 1265.66 (php + ab + lighttpd) + + + all : 1647.72 (3 * php + ab + lighttpd) + all(nice): 1904.40 (same as all, but the local php on grisu + has been 'nice -20'd) + + + if a php is run on the load-balancer it has to get a lower priority than + the load-balancer itself as to handle the work of (here) 3 php-servers. + + +10.10.2003 15:11 +- java ? + + http://jakarta.apache.org/tomcat/tomcat-4.1-doc/jk2/common/AJPv13.html + +08.10.2003 21:08 +- gigE + + Last week 3 RTL8169S gigE cards arrived and were installed in the + test-framework. + + They are very cheap (20 Euros) and are a good start for a low-level + benchmark network. + + First result show us: + + 48 Mb/s with ab. + + The webserver and the test-server are equipped with a 2000+ AMD CPU. The + system load 95%, user load is 3%, the rest is idle on both plattforms. + + After some calculations at gets clear that there are various bottlenecks: + + 1. The PCI-Bus (32bit/33Mhz) can only transfer 133Mb/s + - 48 Mb/s TCP-Traffic results in 55Mb/s Ethernet-Traffic (verified with + slurm) [outgoing] + - there is about 10Mb/s incomming traffic at the PCI bus which the + requests + - the rest of the devices at the PCI bus are eating the last few mb/s + 2. IRQ-Handling + - There are reasons why the RTL8169S cards are so cheap + - they can't send jumbo-frames + - only 8k/64k buffers which results in a interrupt every 3 packets + - they are at least handling checksum offloading for ip, udp and tcp + + In the end there is a need for new hardware to limit the throughput by the + Ethernet again. + - PCI-X, 64bit-PCI, ... + - 'real' gigE-Network cards + - ... + + + BTW: 2.4.22 + the r8139 driver are very flacky and resulted in 3 lock-ups + for today. + + In another test the dual-PPro-FreeBSD 5.1-CURRENT machine has been used as + server with a gigE interface (re0) at has shown that the CPU is the limit + for this combination. The maximum thoughput was 7Mb/s. + + Another small benchmark: + $ ab -n 1000 -c 10 http://192.168.2.41:<port>/lighttpd-20030925.tar + + port | server | CPU Idle + ------+----------------+----------- + 1025 | lighttpd | 75% + 1026 | thttpd 2.23b1 | 75% + 1027 | boa-0.94.14rc17 | 69% * + 1028 | apache 1.3.x | 77% + + + In all 4 cases the thoughput was 8600 kbytes/s. + + * boa had 9 failed transfers. + +- FreeBSD + + The problems with FreeBSD 5.0-RELEASE vanished after updating to + 5.1-CURRENT. + +26.09.2003 18:22 +- FreeBSD + + A SMP-machine has been added to the test-farm. It is running FreeBSD + 5.1-RELEASE and will help to the improve the scalability. + + fixed sendfile() handling. + +- FreeBSD problems + + FreeBSD-5.1-RELEASE-SMP + 2 * Pentium Pro 200 MHz + 192.168.2.38 (doubleheart) + (webserver [lighttpd at port 1025, thttpd at port 1027]) + + Linux 2.4.20 + 1 * AMD 2000+ + 192.168.2.10 (grisu) + (ab) + + Problem: + Connections are reset by the kernel without any application intervention. + + Calling 'ab' (apachebench) at grisu with the following paramters: + + /usr/sbin/ab -n 10000 -c 10 http://192.168.2.38:1025/index.html + + results in the following output: + + ... + Time taken for tests: 40.610 seconds + Complete requests: 10000 + Failed requests: 5980 + (Connect: 0, Length: 5980, Exceptions: 0) + ... + + + This is reproducable and the number of failed requests is always + 5980 +/- 50 requests. In other words: after 4000 requests tcpdump shows + the following output: + + tcpdump shows: + + 00:53:48.923029 192.168.2.10.39774 > 192.168.2.38.1025: S [tcp sum ok] + 1013737315:1013737315(0) win 5840 <mss 1460,sackOK,timestamp 5208461 + 0,nop,wscale0> (DF) (ttl 64, id 7918, len 60) +0x0000 4500 003c 1eee 4000 4006 964d c0a8 020a E..<..@.@..M.... +0x0010 c0a8 0226 9b5e 0401 3c6c 6763 0000 0000 ...&.^..<lgc.... +0x0020 a002 16d0 eeaa 0000 0204 05b4 0402 080a ................ +0x0030 004f 798d 0000 0000 0103 0300 .Oy......... + + 00:53:48.923330 192.168.2.38.1025 > 192.168.2.10.39774: S [tcp sum ok] + 1803860672:1803860672(0) ack 1013737316 win 65535 <mss 1460,nop,wscale + 1,nop,nop,timestamp 4459794 5208461> (DF) (ttl 64, id 6821, len 60) +0x0000 4500 003c 1aa5 4000 4006 9a96 c0a8 0226 E..<..@.@......& +0x0010 c0a8 020a 0401 9b5e 6b84 bac0 3c6c 6764 .......^k...<lgd +0x0020 a012 ffff d4ce 0000 0204 05b4 0103 0301 ................ +0x0030 0101 080a 0044 0d12 004f 798d .....D...Oy. + + 00:53:48.924009 192.168.2.10.39774 > 192.168.2.38.1025: . [tcp sum ok] ack 1 + win 5840 <nop,nop,timestamp 5208461 4459794> (DF) + (ttl 64, id 7919, len 52) +0x0000 4500 0034 1eef 4000 4006 9654 c0a8 020a E..4..@.@..T.... +0x0010 c0a8 0226 9b5e 0401 3c6c 6764 6b84 bac1 ...&.^..<lgdk... +0x0020 8010 16d0 e9c3 0000 0101 080a 004f 798d .............Oy. +0x0030 0044 0d12 + + 00:53:48.924150 192.168.2.10.39774 > 192.168.2.38.1025: P [tcp sum ok] + 1:29(28) ack 1 win 5840 <nop,nop,timestamp 5208461 4459794> (DF) + (ttl 64, id 7920, len 80) + 0x0000 4500 0050 1ef0 4000 4006 9637 c0a8 020a E..P..@.@..7.... + 0x0010 c0a8 0226 9b5e 0401 3c6c 6764 6b84 bac1 ...&.^..<lgdk... + 0x0020 8018 16d0 27e4 0000 0101 080a 004f 798d ....'........Oy. + 0x0030 0044 0d12 4745 5420 2f69 6e64 6578 2e68 .D..GET./index.h + 0x0040 746d 6c20 4854 5450 2f31 2e30 0d0a 0d0a tml.HTTP/1.0.... + + 00:53:48.924728 192.168.2.38.1025 > 192.168.2.10.39774: R [tcp sum ok] + 1803860673:1803860673(0) win 0 (ttl 64, id 6831, len 40) +0x0000 4500 0028 1aaf 0000 4006 daa0 c0a8 0226 E..(....@......& +0x0010 c0a8 020a 0401 9b5e 6b84 bac1 0000 0000 .......^k....... +0x0020 5004 0000 64ba 0000 + + SYN, SYN+ACK, ACK, DATA, RST + + strace shows that no connection attempt has been reported to the + application which is poll()'ing the server socket. + + /* the common loop without any block attempts */ + + accept(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, [0]) = 5 + fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK) = 0 + read(5, "GET /index.html HTTP/1.0\r\n\r\n", 4095) = 28 + stat("/home/jan/lighttpd-0.1.0/servers/", {st_mode=S_IFDIR|0755, st_size=512, ...}) = 0 + fstat(6, {st_mode=S_IFREG|0644, st_size=4348, ...}) = 0 + write(5, "HTTP/1.0 200 OK\r\nConnection: clo"..., 235) = 235 + write(2, "1064875136: (network.c.210) 235 "..., 33) = 33 + syscall_393(0x6, 0x5, 0, 0, 0x10fc, 0, 0xbfbff2c0, 0) = 0 + close(5) = 0 + + /* no futher waiting connections */ + + accept(3, 0xbfbff700, [1852702730]) = -1 EAGAIN (Resource temporarily unavailable) + + /* enter the main-loop */ + + gettimeofday({1769235301, 1663069807}, NULL) = 0 + poll([{fd=3, events=POLLIN}], 1, 1000) = 0 + gettimeofday({4294967295, 65537}, NULL) = 0 + poll([{fd=3, events=POLLIN}], 1, 1000) = 0 + gettimeofday({4294967295, 65537}, NULL) = 0 + poll([{fd=3, events=POLLIN}], 1, 1000) = 0 + ... + + (strace is broken for accept() and gettimeofday(), syscall_393() is sendfile()) + + after entering the main-loop the webserver doesn't receive any new POLLIN + events at all for the next 10-15 seconds. Any connection-attempt within + the period is, as you can see in the tcpdump output, accepted by the + kernel and the received data is thrown away as the kernel sends a RST. + After those 10-15 seconds the application gets a POLLIN event for the + server socket and the normal data-transfer taken place for the next 4000 + requests. + + This behaviour is reproducable with thttpd 2.20c and the current lighttpd. + + +26.09.2003 11:42 +- localizer + + ported the localizer-server application into a module for lighttpd. After + 30 minutes the code was ported and funtional. This modules provides access + to the localizer-db by a HTTP-Interface and generates the response as + HTML, CSV and plain-text. + + So far, the module-interface looks good and flexible enough. + + 7000 req/s is good enough too. Generating the HTML and querying the DB + needs some time. + +22.09.2003 08:40 +- modules + + introduced a simple module interface which allow to hook into the process + of handling the requests. Basicly it allows to move the url-rewriter, the + auth-sub-system and the cache out of the main-code. The module-interface + will be extended to allow the cgi and the fastcgi sub-processes to be + moved into a module. + +15.09.2003 09:36 +- error-handling + + fastcgi and cgi connections are now closed correctly if the corresponding + client-connection has died. + +14.09.2003 10:40 +- cgi + + finally streaming works with CGI, too + + this enable support for cgiirc.sf.net and friends which use streaming as + there transport mechanism. + + streaming has been verified with cgiirc-0.5.2. + +- fdevents + + there where some reports that sigio didn't work as expected. It just + reported no events at all. Looks like a known bug in the glibc on + those platforms. + + Using poll() instead solve the problems. + +27.08.2003 22:12 +- rewrite + + a pcre based rewrite engine has been integrated + +- cgi + + the file-based cgi interface has been replace by two pipes. + +23.07.2003 13:29 +- fdevents + + the whole fd-event handling has been reworked and several smaller bugs + and design-errors have fixed. + + sigio, poll() and select() are working fine again. + + On IRIX 6.5 SIGIO could be used, but without further testing poll() is used. + +- cgi + CGI-support is still broken. + +21.07.2003 18:46 +- dnotify + + using the F_NOTIFY feautre of the Linux 2.4.x kernel gives anothre nice + performance boost as lighttpd can cache the stat()'s the right way, now. + + 23009.66 @ 5-4k-k which means +10% against 30.06.2003 14:03 + + for 1000-4k-nok the performace nearly doubles: 3730.23 goes up to 6144.39 + +17.07.2003 13:21 +- FreeBSD + + a small patch (#include <>) to compile lighttpd on FreeBSD again. + +08.07.2003 10:48 +- fileinfo-cache + + A reallife test showed that the cache wasn't perfect at all as it made + lighttpd crash. This is fixed now. + +30.06.2003 17:40 +- lighttpd-bench + + After some problems with other benchmarking tools for webservers the first + version of lighttpd-bench has been written. + + It a revealed a nasty strange behaviour which was fixed by increasing the + listen-backlog from 5 to 1024. + +30.06.2003 14:03 +- fileinfo-cache + + the fileinfo-cache has been relaxed a little bit and there are always 2 + fstat()'s per file-request now. This isn't that dramatic: + + 21800.74 req/s is still a very good result (7-4k-k). + +29.06.2003 03:29 +- fileinfo-cache + + a fileinfo cache has been added to reduce the number of system-calls to + stat and open a file. + + in the releases before the same file was stat'ed at least 2 times plus a + stat on the docroot for each request. Now the stat()'s and the + corresponding open() + close() calls are cached and the number of system + calls has been reduced to the minimum: + + $ strace -eopen,stat64,read,write,sendfile,accept,shutdown,close \ + -p `pidof lighttpd` + +/* first connection */ +accept(3, {sin_family=AF_INET6, sin6_port=htons(56211), + inet_pton(AF_INET6, "::ffff:192.168.2.10", &sin6_addr), sin6_flowinfo=0, + sin6_scope_id=0}, [28]) = 5 +accept(3, 0xbffff470, [28]) = -1 EAGAIN +read(5, "GET /index.html HTTP/1.0\r\nUser-A"..., 4095) = 91 +stat64("/home/weigon/projects/lighttpd/servers/grisu.home.kneschke.de:1025/pages/", + {st_mode=S_IFDIR|0755, st_size=3656, ...}) = 0 +stat64("/home/weigon/projects/lighttpd/servers/grisu.home.kneschke.de:1025/pages//index.html", + {st_mode=S_IFREG|0644, st_size=4348, ...}) = 0 +open("/home/weigon/projects/lighttpd/servers/grisu.home.kneschke.de:1025/pages//index.html", + O_RDONLY) = 6 +write(5, "HTTP/1.0 200 OK\r\nConnection: clo"..., 235) = 235 +sendfile(5, 6, [0], 4348) = 4348 +shutdown(5, 1 /* send */) = 0 +close(5) = 0 + +/* second connection */ +accept(3, {sin_family=AF_INET6, sin6_port=htons(56212), inet_pton(AF_INET6, + "::ffff:192.168.2.10", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) + = 5 +accept(3, 0xbffff470, [28]) = -1 EAGAIN +read(5, "GET /index.html HTTP/1.0\r\nUser-A"..., 4095) = 91 +write(5, "HTTP/1.0 200 OK\r\nConnection: clo"..., 235) = 235 +sendfile(5, 6, [0], 4348) = 4348 +shutdown(5, 1 /* send */) = 0 +close(5) = 0 + + + In the end we have a new speed record: + + (ab -c 8 -n 100000 http://192.168.2.10:1025/index.html with poll()) + Requests per second: 23435.67 [#/sec] (mean) + + This is a speed increasement of 20-30% against the last internal benchmark. + + +28.06.2003 02:30 +- caching framework + + the trigger.if directive is working fine. the basic algebra is done ( +, + -, *, / and comperations =, <=, >=, >, < and the boolean logic + && and || including braces) + + two basic function are available: + - unix.time.now + - file.mtime(...) + +25.06.2003 17:33 +- caching framework + + added output.include, output.content-type for a cache-hit and + trigger.handler for a cache-miss. + + the actual decision is made be trigger.if which will be added tomorrow. + +15.06.2003 15:33 +- sig-io + + After several other smaller optimisations lighttpd performs better with + sig-io under high load: + + sigio+poll poll + c1000-4k-nok : 2635.95 1643.39 + c1000-4k-k : 7335.68 6788.87 + c1000-100k-nok: 2353.49 1217.73 + c1000-100k-k : 3097.89 2080.73 + + + The user-space part has been optimized a lot. Now it is time to optimized + the number of context-switches between user and kernel-mode. + + user 0m0.180s + sys 0m1.160s + + a fileinfo-cache is the way to go. + + struct { + buffer *name; + struct stat st; + int handler; + ... + + int fd; + void *mmap_p; + } + +11.06.2003 14:57 +- sig-io is back again + + and it works fine. + + sigio (Realtime-Signals under Linux 2.4.x) sends one signal per event and + buffers the rest of the events in a kernel queue. If the is full a SIGIO + is sent and poll() gets all events at once for further processing. + + + Currently the behaviour is a little bit strange: + - sig-io + poll() is good for non-keep-alive connections + - poll() for keep-alive connections + + c1000-4k-nok: (1000 concurrent request, 4k filesize, no keepalive) + poll : 1521.38 + sigio+poll: 2124.00 + + c1000-4k-k: (1000 concurrent request, 4k filesize, keepalive) + poll : 5882.35 + sigio+poll: 1239.46 + + Very strange for now. + +09.06.2003 23:59 +- code-cleanup + + the event-handling code has been rewritten to handle single events better + as they are expected from sig-io. + + the fallback-mode of sig-io is broken now, but the normal poll() mode got + a 10% increasement in speed. This means the we are back the speed level of + 20030308-0155 are as fast as zeus again. + + Especially under higher load the current lighttpd performes better. + +09.06.2003 11:51 +- sig-io benchmark + + 20030609-1151 20030608-2110 20030308-0155 +-c 10 sigio select() poll() poll() +4k : 7870.92 7937.77 8035.36 9443.76 +4k (keep-alive) : 14098.41 14590.02 14275.52 17985.61 +100k : 3366.32 3382.03 3261.15 3722.32 +100k (keep-alive) : 5544.77 5576.00 5573.20 5975.86 +-c 100 +4k : 6144.77 5821.40 5714.29 6724.95 +4k (keep-alive) : 9097.53 9213.19 8979.08 10833.06 +100k : 2549.33 2495.94 2318.95 2607.36 +100k (keep-alive) : 4267.67 4283.94 4094.17 4314.06 + + +For -c 100 4k and 100k sig-io gives a small increasment. + +09.06.2003 01:00 +- sig-io + + writing large files works now as expected. After removing the limit for + the chunks of sendfile the write-buffer-is-empty-again signal is + generated. that was missing. + + 321 of 10000 connections still have the wrong length. After that is fixed + it is time to start some benchmarks again. + +08.06.2003 21:10 +- sig-io + + first attempts in getting SIGIO support running which were not very + successfull yet. + +- poll()/select() benchmark + + 20030608-2110 20030525-1623 20030308-0155 +-c 10 select() poll() poll() poll() +4k : 7937.77 8035.36 8166.60 9443.76 +4k (keep-alive) : 14590.02 14275.52 14781.97 17985.61 +100k : 3382.03 3261.15 3176.42 3722.32 +100k (keep-alive) : 5576.00 5573.20 5809.56 5975.86 +-c 100 +4k : 5821.40 5714.29 5669.26 6724.95 +4k (keep-alive) : 9213.19 8979.08 8418.22 10833.06 +100k : 2495.94 2318.95 2314.28 2607.36 +100k (keep-alive) : 4283.94 4094.17 4456.92 4314.06 + + + as the overall processing cycle has been rearranged the overall figures + changed in minor ranges. c100-4k-k increased, *-100k-k decreased. + + At least it still works. + +08.06.2003 12:45 +- select()/poll() + + implementate an abstration layer for fd-events (like eevry other webserver). + currently we support poll() and select(). + + This should bring us support for Mac OS X and propably Windows. + +04.06.2003 18:35 +- configure + + lighttpd can now be build without ssl-support even if openssl is installed + +- protability + + - on IRIX IPv6 is supported now out of the box + - for FreeBSD some missing haeders have been added + +04.06.2003 14:34 +- pipelining + + adding support for pipeling introduces a problem if the request-header + was broken. this has been fixed now. + +- unneccesary slashes + + when building pathes from different portions of a string (e.g. docroot + + virt-server-docroot + request-uri) slashes where added for security and + simplicity. This resulted in up to 5 adjacent slashes which caused no harm + but looked strange. (fixed) + +04.06.2003 09:57 +- start/stop messages + + the error-log contains the start and end-times of the lighttpd process, + now. + +- configfile + + the config-file parser has been relaxed to accepts tabs instead of spaces. + +- better error-handling + + fixed a <a + href="http://bugs.php.net/?id=24009">bug in the FastCGI-SAPI of PHP</a> + + if the fastcgi process dies or closes the connection unexpectedly we + return 500 now instead of closing the connection to the client. + +- Location + + the CGI/1.1-rev-03 specification requires us to send Status 302 if a + Location-header is sent by the client and a Status-header is missing. + +- PATH_TRANSLATED + + if PHP is compiled without --discard-path PATH_TRANSLATED has to be + provided. + +27.05.2003 15:54 +- directory listings + + if a directory is requested and the directory doesn't contain a index-file + a directory-listing can be displayed. You have to enable directory-listings + in the config-file (directory-listings on) + +- url-decoding + + up to know urls where not decoded at all (%26 -> . and so on). This has + been added. Unicode isn't supported as we use 8-bit chars internally. + +26.05.2003 00:44 +- pipelining + + as Sascha required pipelining for his benchmarking tool it has been + implemented. + + pipelining allows sending a bunch of requests at once without waiting for + the actual responses. This reduces the network-overhead and the + round-trip-time. + +- non-free()d memory + + dmalloc helped to close some non-free()d memory. For the normal operation + this isn't important as only memory chunks which had to be free()d at the + end of the live-time of the lighttpd-process were not de-allocated. + +- partly-initialized variables + + lighttpd wasn't initializing the main-structure which resulted in strange + behaviour in rare circumstances. + + +25.05.2003 16:23 +- benchmarks + + after removing some useless internal copies we are more or less at the old + speed levels. + + after adding virtual-hosts 10% of the performance were lost. Using less + memcpy() operations might add several other boosts. + + I've just checked how lighttpd compares to Zeus. + + lighttpd (current) (old) +-c 10 20030525-1623 20030308-0155 Zeus 4_2 +4k : 8166.60 9443.76 7278.55 +4k (keep-alive) : 14781.97 17985.61 16496.21 +100k : 3176.42 3722.32 3156.37 +100k (keep-alive) : 5809.56 5975.86 5460.30 +-c 100 +4k : 5669.26 6724.95 5134.26 +4k (keep-alive) : 8418.22 10833.06 8010.25 +100k : 2314.28 2607.36 2688.32 +100k (keep-alive) : 4456.92 4314.06 4240.70 + +23.05.2003 14:38 +- cgi-variables + + HTTP_HOST was missing for the cgi-module while the fcgi-module passed it + through to the handler. Fixed that. + +- fcgi-errors + + the connection to the fcgi was dropped and poll() reported an error, the + error wasn't reported to the client the right way. + +22.05.2003 23:02 +- authorization + + the first password-storage has been added: + + [auth] + backend plan + plain-userfile <filename> + + require /download/ user=jan|user=anom + http-auth /download/ "download archiv" digest + + groups are prepared but not implemented. basic and digest are working fine. + +20.05.2003 17:53 +- authentification + + The auth-methods from RFC 2617 have been added. + - auth basic + - auth digest + + The only source for accounts is currently only the config-file. + + auth-digest needs the plain-text passwort. Are there any source which + provide a plain-text password ? + +12.05.2003 14:33 +- virtual hosts + + added very basic virtual-host support + + virtual-server-root /home/weigon/projects/lighttpd/servers/ + virtual-server-default-host grisu.home.kneschke.de:1025 + virtual-server-docroot /pages/ + + docroot is + + - if http-host exists + <virtual-server-root> + <http-host> + <virtual-server-docroot> + + - otherwise + <virtual-server-root> + <virtual-server-default-host> + + <virtual-server-docroot> + + - if even virtual-server-default-host does not exist, 500 is sent + + +12.05.2003 13:02 +- code cleanup + + After two month of development it was time clean-up the internal + structures. It looks like every went fine as lighttpd works es expected + like before. + +- deflate + the on-the-fly compression has been verified to work fine with opera, + konqui, mozilla and the IE. + +12.05.2003 02:10 +- on-the-fly compression: deflate + + Why the hell are the defining a "deflate" encoding in the form of + + _deflate_ The "zlib" format defined in RFC 1950 [31] in combination + with the "deflate" compression mechanism described in RFC 1951 [29]. + (RFC 2616) + + and noone implements it that way ? Konqui and Mozilla expect a plain + deflate() package without the zlib-header. + + Konqui is using "inflate2(..., -MAX_WBITS); " which is noted in the zlib + source as + + /* handle undocumented nowrap option (no zlib header or check) */ + + Funny. Very, very funny. + + + Anyway. We have mimetype-depended compression support now. + +11.05.2003 21:56 +- logging + re-arranged the logfile structure to write CLF + useragent + referrer. + +11.05.2003 10:23 +- POST file-upload + added the missing functionality to send more than a single packet to the + FCGI-Server (or Client ? ... the PHP). + + This gives us the file-upload thing for eg. PHP and large user-forms + (>16kb). + +05.05.2003 15:21 +- PATH_INFO + + added support for PATH_INFO. PHP is a little strange and doesn't trust the + passed PATH_INFO setting. Works fine now. + +30.04.2003 15:25 +- bug-fixing day + + While testing the FastCGI interface with the MSIE Björn Schotte discovered + that sometimes the output repeats itself from the start in an endless loop. + This bug has been fixed. + + The read-write-fallback for ancient systems seeked the wrong FDs. + + The FastCGI handler is now separting the HTTP-Header from the + response-body what results in a cleaner interface. The "header too long + for caching" message is gone now. + +28.04.2003 18:18 +- chunked transfer-encoding + + The FastCGI part is now using Chunked-Transfer-Encoding if HTTP/1.1 is + used and no content-length is specified. + +27.04.2003 23:10 +- chunked transfer-encoding + added "Transfer-Encoding: chunked" which is currently used in the web-chat + for the endless stream. Perhaps it helps some browsers. + + The FastCGI interface will get a the chunked-support too, as it will + enable keep-alive even if no content-length is returned from the FastCGI + process. We know the size of the chunks and will report it to the browser. + +16.04.2003 12:02 +- gigE deatchmatch + + sascha compared the current lighttpd to his premium thttpd in his + gigabit-Ethernet-Network. + + <a +href="http://schumann.cx/gbit_deathmatch.txt">http://schumann.cx/gbit_deathmatch.txt</a> + + small comment: + lighttpd provides the same performance (req/s and thoughput), but uses more + CPU-Time. + +10.04.2003 17:22 +- works on IRIX + + Jörg Behrens provided me login to his SGI Origin and after fixing two small + typos it worked fine on IRIX. (#if define HAVE... was missing a 'd' and + getopt returns a 'int' and not a 'char') + + IPv6 support is currently disabled for IRIX a gethostbyname2() isn't + available. + +10.04.2003 15:56 +- another bug-fixing day + The upper limit of open connections was enforced and a caused a seg-fault. + The current limit is set to 4096 parallel connections. + + As sascha benchmarked lighttpd and his premium thttpd I tried to reproduce + his reported results and had to fix 2 flaws in the async-io handling of + httpd_load which was used for the testing. + + If sascha starts another benchmark session I'll put a link to it here. + +09.04.2003 00:08 +- bug-fixing time + Date: and Last-Modified: where sending a timezone != GMT what was invalid. + The timestamp itself was correct, just the timezone use the wrong + characters. + + the fcgi-code had an buffer-overflow for larger POST-Requests. + + The fcgi-code still has problems with POST-Request larger than 16kb as the + the write buffer is full. Currently we don't handle this case except from + reporting it in the error-log. + +02.04.2003 01:17 +- cgi is back + + The CGI Interface wasn't tested for a longer time. It don't really + survived the introduction of the config-file handling and the internal + changes that were part of it. + + A small test with + +#! /usr/bin/perl +print "Content-Type: text/html\r\n\r\n"; +print time()."\n"; +0; + + produced 219 req/s. + + Calling PHP via the CGI interface results in 100 req/s. Same script, same + parameters for 'ab' and same PHP result in 1400 req/s if we use the + FastCGI interface. + + <b>Don't forget</b>: these benchmarks only represent figures for scenarios which + are not very realistic. They are just usefull for comparisions of the + internals. We don't want to benchmark applications. + +01.04.2003 23:04 +- new benchmarks + + This time we wanted to see if we can get the fastcgi-Interface a little bit + faster. We use this small script for the testing the interface. + +<?php + +ob_start(/*"ob_gzhandler"*/); +print "12345<br />\n"; +header("Content-Length: ".ob_get_length()); +ob_end_flush(); + +?> + + It generate a small network load and is the best case for benchmarking the + overhead of the call to the fastcgi-php. + + Using the chunk-API instead auf tmp-files increase the req/s vom 1200req/s + to 1600 req/s and the IO-load went down as expected. Waiting for the + filesystem and creating 1200 files per second required some time. The + CGI-Interface is still using tempfiles. + +01.04.2003 19:28 (no april fools joke) +- added SSL support + I took -lssl and -lcrypto and added SSLv2/SSLv3/TLS support to lighttpd. + + After some reading I realisized that libcrypto contained some of my code. + They have a similar buffer-struct with similar functions and they use the + same MD5-code from the RFC. :) + + Adding basic SSL-support was quite esay: replacing all write/read-calls by + SSL_write/read, initializing the ssl-context the right way and telling + openssl where to get the data from (SSL_set_fd). It fits really well into + lighttpd. + +- sidenote + sooner or later the con->filename handler will vanish in favour of the + direct interface to the chunk-API which will simplifiy the design a little + bit. + +31.03.2003 20:50 +- added a web-chat module + 1400 lines of C-code are neccesary to add a web-chat to lighttpd. + + Features: + - bb-code for text-layouting + - auto-highlight for URLs and Email-adresses + - unlimited number of channels + - max. 32.000 users per channel + - fast as it is directly integrated into the web-server + - easy to install (as easy as lighttpd) + - IRC-like (/msg, /kick, /me, ...) + - works with MS IE, Mozilla, Konqueror, ... + - uses as little JavaScript as possible to stay compatible with all browsers + - flood-protection (2 Levels: ignore and kick) + - uses CSS for customized look-and-feel + +25.03.2003 00:36 +- finished the config-file support + now we have config-sections + + [fastcgi] + fastcgi .php 192.168.2.76 1025 + + The only this missing is -HUP handling to re-read the config. But that + will follow when I'm up again. + +20.03.2003 01:04 +- adding config-file support + the most boring part of the writing programs after writing documentation. + The format is quite simple and good enough for lighttpd. + +19.03.2003 03:05 +- more on sendfile support + added support for freebsd's version of sendfile(). + + Linux: + ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); + + FreeBSD: + int sendfile(int fd, int s, off_t offset, size_t nbytes, + struct sf_hdtr *hdtr, off_t *sbytes, int flags); + + the first 2 params are swap, the next 2 are the same and the last 3 are + set to NULL or 0. + + Solaris 9 is providing a direct interface to the chunk-API with the + sendfilev() system call: + + ssize_t sendfilev(int fildes, + const struct sendfilevec *vec, int sfvcnt, size_t *xferred); + + recovering from a failure is a little bit more complex, but it should be + fast. I just need a system to test it. Anyone ? + +18.03.2003 17:32 +- in the news + what a surprise: lighttpd is announced in entwickler.com. + + <a href="http://www.entwickler.com/itr/news/psecom,id,9483,nodeid,82.html">http://www.entwickler.com/itr/news/psecom,id,9483,nodeid,82.html</a> + + this was not planned. + + +- implemented the chunked-API + rather writing the content of multi-range'd requests to a temp-file we + just register the parts in a chunk-queue. + + chunk 1 [mem] - part-header + chunk 2 [file] - source-file (offset, len) + chunk 3 [mem] - part-header + chunk 4 [file] - source-file (offset, len) + chunk 5 [mem] - part-footer + + If everything is prepared, the content-length is calculated and the + http-header is generated and prepended to the chunk-queue. + + the handle_write() function which handles the write-process just sends the + chunks to the network without only further modifications. + + the chunk-api even simplifies the + 'my-fastcgi-process-needs-some-time-to-create-the-response- + and-sends-the-data-in-chunks' case. Just add the chunk to the queue if it + is received. + +13.03.2003 10:49 +- another set of real-life benchmarks + + the chairman was benchmarked a little bit. Chairman is full-flegded WCMS. + As every CMS it needs some time to generate pages. To speed the page + generating the static-parts of the page can be cached internally. + + without caching with caching +apache + mod_php : 2.50 10.64 req/s +lighttpd + fcgi-php: 2.50 22.74 req/s + + Looks like lighttpd is the way to go :) + + Please, don't compare these numbers with the other benchmarks: + We used a different test-machine which is a little bit under-powered + for this test-case. + +12.03.2003 13:43 +- another large application works with lighttpd + + <a +href="http://www.thinkphp.de/content/content2.php?CatID=44&NewsID=95">Chairman</a> from <a href="http://www.thinkphp.de/">thinkphp</a> has been verified to work with lighttpd. + + Three things are worth to note: + + 1. ALWAYS use log_error in the php.ini if you use FastCGI + otherwise it will put the errormessages directly into fastcgi-socket + without any FastCGI Header. This will result in strange errors. + (its a PHP bug) + 2. DOCUMENT_ROOT has been added to the environment variables + 3. a small bug for sending larger output from a fastcgi client has been + fixed. + +11.03.2003 11:52 +- added two neccesary features + - redirect _dir_ to _dir_/ + - append index-file to _dir_/ if they exist + +11.03.2003 00:13 +- another target + after adding some headers lighttpd compiles without any warnings + under cygwin on Windows. + + A native windows port will take some time as I have to figure out the + changes on the winsock.h to the unix headers. + +10.03.2003 11:52 +- added Solaris 8 and 9 + lighttpd has been confirmed to compile and work on + + - linux + - FreeBSD, NetBSD + - Solaris 8, 9 + + Perhaps I can get lcc to compile it on windows. + +10.03.2003 01:30 +- another platform was confirmed + lighttpd works on NetBSD + +- another webserver benchmark + mathopd (http://www.mathopd.org/) + + Mathopd/1.4 +-c 10 +4k : 6329.11 [#/sec] +4k (keep-alive) : 10235.41 [#/sec] +100k : 1168.50 [#/sec] +100k (keep-alive) : 268.82 [#/sec] (99% idle) +-c 100 +4k : ---- (connections dropped) +4k (keep-alive) : ---- +100k : ---- +100k (keep-alive) : ---- + +Very good values for small files, but the rest ? + +10.03.2003 00:06 +- added more write-handlers + 1. sendfile (linux only) + 2. mmap + write + 3. read + write + + This means that lighttpd runs on some more systems. It has been verified + to compile and work on linux 2.4.x and FreeBSD. + + +-c 100 + sendfile mmap read +4k 6476.68 5698.01 5363.08 [#/sec] +100k 2312.35 841.54 783.09 [#/sec] + + The mmap() numbers could be better with a fd+mmap-cache. The zero-copy thing + is good for our performance. + +- tested another small httpd - BOA + just to get a better view about the performance of lighttpd i've tested + another httpd: Boa/0.94.14rc16 (http://www.boa.org/) + + Boa/0.94.14rc16 +-c 10 +4k : 5659.63 [#/sec] +4k (keep-alive) : 250.23 [#/sec] +100k : 1104.29 [#/sec] +100k (keep-alive) : 1363.14 [#/sec] +-c 100 +4k : 4319.65 [#/sec] +4k (keep-alive) : 2490.66 [#/sec] +100k : 815.93 [#/sec] +100k (keep-alive) : 1007.05 [#/sec] + + The results are a little bit strange. Keep-Alive for small files seems to be + broken. + + It looks like lighttpd has enough power for now. Time the port and add more + features. + +09.03.2003 13:26 +- load-balancing works great + + I used the framework from http://framework.netuse.de/ as a + real-life example again to test the load-balancer. + + setup: + - grisu + - AMD athlon XP 2000+ + - runs lighttpd + ab + a 'nice -15'ed FastCGI-PHP + - generates 153 req/s alone + + - laptop + - Intel PIII 850 + - runs a FastCGI-PHP + - generates 88 req/s + + using the internal load-balancer of lighttpd which balances the + php-requests over grisu and laptop generate: + 221 req/s + + An apache with mod_php running on grisu gives 117.04 req/s only. + +09.03.2003 12:46 +- building the connect connection to the fcgi-php is not non-blocking + socket() -> connect() -> fcntl(non-blocking) resulted in some problems as + the connect() call blocked sometimes for 1 seconds. + + socket() -> fcntl(non-blocking) -> connect() solves this problem, but + addes more overhead. The first connect-attempt seems always to + return EINPROGRESS. + +08.03.2003 15:06 +- the first 'real-life' PHP test showed 2 bugs in the POST handling. + 1. the Content-Type header was not forwarded via fastcgi + 2. the internal content_length handler wasn't reset after the request + + now lighttpd + php work with + + http://framework.netuse.de/ + + the basic application gives us 145 req/s + +08.03.2003 11:06 +- use diet to 'test' your code + diet gives some use full warnings which should be followed. I took the + chance and removed the last few fprintf() and sprintf() from the code. + now, we don't need stdio.h anymore. + + a staticly linked, stripped lighttpd is 42884 bytes large. + +08.03.2003 01:55 +- buffer_strcat() uses strlen() to get the length of the 'to-be-catted-string' + if the string is constant or has a known length you can pass that function + which gives use buffer_strcat_len() which passes the string length as a + parameter. + +- using a ramdisk as the base for the tempfiles generated mkstemp() is a + very cood idea + it reduces the io-load on the system and gives as more cpu-time for + load-balancing the php-requests. + +- cache the output of strftime(), gmtime() and localtime + we generate more then 10.000 req/s which results in using the same + timestamp over and over again. It is sufficient to generate the timestamp + once a second and give us a bunch of CPU-cycles for sending files. + + the same applies for other timestamps like Last-modified which sends a + string version of st.st_mtime. Cache it. + +- don't try to overoptimize for code for the sake of clean code + the range support require some hack in the first versions like setting the + write_offset to the start of the range and keeping the rest of the + "code 200" send code as usual. Only a few lines added this first range + feature. fast, but a hack. It made things more complicated then neccesary + to add multi-range support in this scenario. + + removing the hack doing small clean preprocessing helps to clean up the + whole 'write a chunk on the wire' code. At the end we use less code and + have a cleaner design. + + Sascha proposed a chunked layer which just takes the chunks (http-header, + part-header, part-message, ... and the last boundary) and passes them to + the write-to-the-wire-code. This is far more elegant and will perhaps same + us from other problems, too. Currently I think that it is not necessary. + First we have to fix some protability issues. + +Ok, today benches: + + today last +-c 10 +4k : 9443.76 7739.94 + 22% +4k (keep-alive) : 17985.61 13885.03 + 29% +100k : 3722.32 3349.97 + 11% +100k (keep-alive) : 5975.86 4965.49 + 20% +-c 100 +4k : 6724.95 5918.56 + 13% +4k (keep-alive) : 10833.06 8405.48 + 28% +100k : 2607.36 2393.60 + 9% +100k (keep-alive) : 4314.06 4035.35 + 7% + +Top Transfer Rate: + + 86365.77 [Kbytes/sec] received + +Too much for a GigE-link ? + + +so far: + +- use state-engines + + a non-forking webserver like described in + + http://www.kegel.com/c10k.html + +- don't use fprintf() for logging. + writing one line to log file takes more time that sending responsing the a + HTTP-request + +- take care of your memory + + typedef struct { + char *ptr; + size_t used; + size_t size; + } buffer; + + buffer* buffer_init(); + void buffer_free(buffer *b); + int buffer_prepare_copy(buffer *b, size_t size); + int buffer_prepare_append(buffer *b, size_t size); + int buffer_strcpy(buffer *b, const char *s); + int buffer_strcpy_len(buffer *b, const char *s, int s_len); + int buffer_strcat(buffer *b, const char *s); + int buffer_strcat_len(buffer *b, const char *s, int s_len); + + buffer_init() is only called once per buffer. If you don't need the + buffer, set 'used' to zero and reused it afterward. + buffer_strcpy() + buffer_strcat() check if the 'used' + the new strlen() + fit in to the 'size' of the buffer. If not the realloc() the buffer. + buffer_strcat() uses memcpy(ptr + used - 1, s, s_len + 1); which should + be faster than the original strcat(). + + Using those buffers keep the memory usage at 2Mb. malloc() and free() are + only called at the beginnig of the programm and at the end. + +- don't used sprintf() to convert a integer into a string as like + sprintf(buf, "%d", l); + + write your own ltostr function. sprintf() is to general for the case and + is slow. + + +Speed: +------ + +Let's assume that <a +href="http://www.acme.com/software/thttpd/">thttpd</a> is (one of) the +fasted webservers out there. + +all httpds were started with: + +$ ./thttpd -p 1026 -D \ + -d /home/weigon/projects/localizer/src/lighttpd/docroot/ \ + -l thttpd.access.log + +(lighttpd uses the same parameters). + +We used ApacheBench for testing the RPS (requests per second). + +$ /usr/sbin/ab -dS -c 10 -n 100000 http://192.168.2.10:80/dummy.out + + lighttpd thttpd/2.21b+php thttpd/2.20c thttpd/2.23b1 +(concurrency 10) +4k : 7739.94 6040.84 5078.20 5888.24 [#/sec] +4k (keep-alive): 13885.03 10349.82 5034.49 5853.09 [#/sec] +100k : 3349.97 1176.29[1] 1188.74 1198.29 [#/sec] +100k (keep-a.) : 4965.49 2513.38[2] 1100.65 1130.51 [#/sec] +(concurrency 100) +4k : 5918.56 4907.01 4987.53 4886.87 [#/sec] +4k (keep-alive): 8405.48 6379.99 4938.76 4816.26 [#/sec] +100k : 2393.60 972.73 958.27 961.09 [#/sec] +100k (keep-a.) : 4035.35 893.51[3] 970.21 955.05 [#/sec] + +thttpd/2.21b+php has been patched with the keep-alive + php patches from PHP4 +sapi/thttpd/thttpd_patch. + +[1] ab reported: +Failed requests: 1 + (Connect: 0, Length: 1, Exceptions: 0) +[2] ab reported: +Failed requests: 5 + (Connect: 0, Length: 5, Exceptions: 0) +[3] ab reported: +Failed requests: 24 + (Connect: 0, Length: 24, Exceptions: 0) + +Tunning the thttpd: +------------------- +- disable symlink checking (chroot() or -nos) +- log to /dev/shm/logfile, a ramdisk or disable logging + + +$ ./thttpd -p 1026 -D \ + -d /home/weigon/projects/localizer/src/lighttpd/docroot/ \ + -l /dev/null + diff --git a/INSTALL b/INSTALL new file mode 100644 index 00000000..0f78d7f8 --- /dev/null +++ b/INSTALL @@ -0,0 +1,32 @@ + +============ +Installation +============ + +:author: Jan Kneschke +:Date: $Date: $ +:Revision: $Revision: $ + +Installation +------------ + +Get the source from + +http://www.incremental.de/products/lighttpd/download/ + +unpack it by :: + + $ gzip -cd lighttpd-1.0.0.tar.gz | tar xf - + +compile and install it with :: + + $ cd lighttpd-1.0.0 + $ ./configure + $ make + $ su - + # make install + # exit + +take look at the configfile in ./doc/lighttpd.conf, +make your own copy of that file and modify it for your needs. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..a92affeb --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS=src doc tests debian cygwin openwrt + +EXTRA_DIST=lighttpd.spec @@ -0,0 +1,316 @@ + +==== +NEWS +==== + +- 1.3.10 - 2005-02-06 + + * added support for full commandline in spawn-fcgi + * fixed missing check for IP-address in mod_fastcgi + * fixed compile error with openssl in mod_fastcgi + * removed a debug-message from network_freebsd_... + +- 1.3.9 - 2005-02-06 + + * added a stricter URI parser + * added a check to the CGI spawner if the cgi-handler exists + * added documentation for SSL and mod_status + * added handling of startup environment to FastCGI + * improved performance in FastCGI in buildind the FastCGI header + * fixed min-procs and max-procs in FastCGI on PowerPC + * fixed crash in setenv.add-response-header + * fixed handling of nph-scripts in CGI + * fixed accidently sending out physical file in CGI on error + * fixed cygwin support + * fixed handling of missing files + * fixed HEAD requests for dynamic requests + +- 1.3.8 - 2005-01-30 + + * added traffic shaping by remote host and virtual server + * added auto-spawning of FastCGI process on demand + * added virtual host based on MySQL + * added mod_setenv to add envirnoment and http headers on the fly + * added support for syslog in mod_accesslog + * improved output of mod_status + * improved debug output in request handling + * fixed build problems on netbsd 1.4.x and 1.5.x + * fixed status.url configuration + * fixed handling of != and !~ in configutation + * fixed special cases in keep-alive handling + * fixed timeout handling in handling POST requests + * fixed mode AUTHORIZER in FastCGI + * fixed handling if internal redirects if no Host: is supplied + * fixed mod_alias + pathinfo + * fixed directory indexes and permissions + * enabled sending errorlog to syslog again + +- 1.3.7 - 2004-12-11 + + * added retries for a fastcgi connect if a php-childs + dies at startup + * update the debian directory + * added setgroups() to drop all group-privs + * added native port to windows via mingw32 + * added server.tag = '...' + * added support for ${...} in mod_ssi + * ported all plugins to conditional support + * fixed multipart handling in cgi + * fixed kqueue event-handler + * fixed wrap-around in mod_status + * fixed crash with SSL + FastCGI + * fixed detection of SSL headers + * fixed handling of dangling SSL_shutdown + * fixed detection of keep-alive of Firefox + +- 1.3.6 - 2004-11-03 + + * added spawn-fcgi to the distribution + * added support in fastcgi module to spawn fastcgi + processes itself + * fixed logfile cycling if external logging is used + * fixed connection handling in fastcgi if no chunk + encoding is used + * fixed internal redirects on directories if a query + string is supplied + * fixed cgi-module for POST request above 4k + * fixed mod_alias and follow-symlink + +- 1.3.5 - 2004-10-31 + + * added mod_alias + * added mod_userdir + * added the exec command to the SSI handler + * added a switch to disable follow-symlinks + * added a switch to disable IPv6 at compile-time + * fixed compilation on FreeBSD and NetBSD 1.3.x + * fixed segfault in pipelining + * fixed a segfault in writev() handler if LFS is used + +- 1.3.4 - 2004-10-24 + + * added limiter for open files + * added logging of user supplied data to accesslogs + * added build target for OpenWRT + * added plain backend support for auth-digest + * fixed handling the external accesslog processes + * fixed SERVER_NAME in CGI and FastCGI + +- 1.3.3 - 2004-10-16 + + * added support for NL terminators in CGI-scripts + * added support for conditionals in mod_auth, + mod_simple_vhost and mod_evhost + * added a error-handler for 404 codes + * fixed request counter in the rrdtool module + * fixed log-file cycling + * fixed seg-fault + +- 1.3.2 - 2004-09-30 + + * fixed file-cache + +- 1.3.1 - 2004-09-30 + + * fixed file-cache + * fixed parsing of IPv6 adresses + * fixed cgi for cygwin + * fixed test-suite for FreeBSD and IRIX + * fixed handling of shrinked files + * fixed handling of REQUEST_URI after rewrite + +- 1.3.0 - 2004-09-17 + + * added build for MacOS X and Cygwin + * added handling of more than one socket + * added config-conditions for User-Agent and Referer + * added final rewrite-rules + +- 1.2.8 - 2004-09-11 + + * added a cache for mimetypes + * added X-Forwarded-For for mod_proxy + * fixed handling of comments in If-Modified-Since + * fixed error handling in FastCGI code + * fixed expire plugin for second Expire header + +- 1.2.7 - 2004-09-04 + + * added mod_rrdtool for internal statistics + * added xattr support + * added user-controlable timeouts + * improved documentation for many plugins + * fixed POST requests for mod_proxy + * fixed rare hang with CGI + * fixed seg-fault if no configfile is specified + * fixed rare problem in FastCGI header generation + +- 1.2.6 - 2004-08-26 + + * added apache-like accesslog definition + * enabled timestamp cache again + * improved performance in the string compare functions + * fixed double-free in fastcgi handler + * fixed error-handling in cgi handler + +- 1.2.5 - 2004-08-10 + + * added skeleton for solaris 10 port-API + * added compression support even if no cachedir is set + * added conditional configoptions + * fixed compilation on OpenBSD + * fixed kqueue support + * fixed pipelining bug + * fixed parallel build (triggered by Gentoo) + * updated debian postinst + +- 1.2.4 - 2004-07-31 + + * added kqueue support + * added server-side includes (mod_ssi) + * fixed large post uploads in fastcgi + * fixed rt-signals handling of delayed events + +- 1.2.3 - 2004-07-10 + + * added a proxy module for Java and friends + * added support to pass accesslog through an external programm + * added mimetypes for text/css and text/javascript + * fixed index-files for FastCGI if webserver is in chroot + * fixed error messages of CGI process fails to exec() + * fixed detection of pcre on IRIX and FreeBSD + * fixed timestamps in Last-Modified checks + * fixed 64bit builds + * fixed mmap-caching of large files + * relaxed the HTTP parser on empty headerfields + +- 1.2.2 - 2004-06-15 + + * added support for unix domain sockets in FastCGI + * fixed mmap caching + * fixed compile-time check for linux sendfile() + * fixed check for pcre.h on Fedora Core 2 + +- 1.2.1 - 2004-05-30 + + * added experimental support for AIX send_file() + * added an mmap cache to the filehandle cache + * enabled FreeBSD sendfile support again + * added support for calling CGI binaries directly + * fixed pipelining for POST requests + * fixed some seg-faults if no configfile is used + +- 1.2.0 - 2004-05-17 + + * added conforming Expect: handling + * added a module for secure and fast downloading + * rewrote the event handling interface + * fixed array handling which might lead to 'missing header' + * fixed pipelining support + * fixed build of the localizer extension + * fixed cgi handling for headers which are flushed to often + * fixed compilation on Solaris 2.5 + +- 1.1.9 - 2004-04-29 + + * added AUTHORIZER mode to the FastCGI module + * added 'check-local' option to disable local stat() in the FastCGI module + * added prefix-notation for FastCGI module + * added 'mod_usertrack' + * improved CGI/FastCGI spec conformance + * more code cleanup + * fixed HTTP/1.1 chunk headers + * fixed POST handling + * fixed SSL network handler + * fixed writev() network handler + +- 1.1.8 - 2004-04-16 + + * code cleanup + * limiting the size of the request-body and the request-header + * minor speed improvements + * tightend the HTTP-Parser again + +- 1.1.7 - 2004-04-12 + + * added REMOTE_USER to the Server->FastCGI parameters + * added bzip2 compression + * improved the error-messages from the new configfile parser + * fixed accesslog writing for errornous requests + * fixed LFS (64bit filesizes) handling + * fixed Content-Length for HEAD requests + * fixed some memory leaks in the configfile parser + +- 1.1.6 - 2004-04-10 + + * tightend the HTTP-Parser + * rewrote the configfile parser (based on lemon) + * fixed openssl support + * fixed mmap+write support + * use localtime in accesslog if possible + +- 1.1.5 - 2004-04-07 + + * added ldap backend to the auth + * added a mod_expire + * added debian packaging structure + * merged redhat and suse spec-file + * fixed eventhandler for solaris + * fixed 64bit fileoffsets + * fixed permissions of the PID-file + +- 1.1.4 - 2004-04-04 + + * added server.pid-file + * added support for solaris /dev/poll and solaris sendfilev() + * added support for writev() + * added PATHINFO support (again) + * fixed CLF logfile writing + +- 1.1.3 - 2004-03-25 + + * set default event-handler to 'poll' + * fixed logcycling in chroot() + * fixed hostname detection + * added syslog() as fallback for error-logging + +- 1.1.2 - 2004-03-22 + + * added a "docroot" setting for fastcgi processes + * performance improvements + * improved configure script + * rewrote the fastcgi config parser + * added a rc-script for RedHat + * added epoll() support for Linux 2.6.x + +- 1.1.1 - 2004-03-15 + + * added localizer module + * performance improvements + * code cleanup + +- 1.1.0 - 2004-03-06 + + * changed some configuration keys for better readability + * moved the virtual-host code to mod_simple_vhost + * added enhanced virtual host plugin from Christian Kruse + * added two new auth-backends (htpasswd, htdigest) + * fixed and improved authentification + * stricter parsing of the Host: field + * added a warning for unused configuration keys + * improved FastCGI documentation + +- 1.0.3 - 2004-02-13 + + * a startup script has been added (LSB compliant) + * HEAD requests were submitting the content like a GET request + * the virtual directory listing got a face-lifting and fixes + * request-headers are now handled case-in-sensitive as required + by the standard. this fixes POST requests for w3m and some Proxies. + +- 1.0.2 - 2004-02-07 + + * rearrangement of the default configfile + * some updates in the documentation + * a entry in the error-log for a 404 + * stdout is no longer the default for the accesslog @@ -0,0 +1,156 @@ + +======== +lighttpd +======== + +------------- +a light httpd +------------- + +:author: Jan Kneschke +:Date: $Date: 2004/11/03 22:25:54 $ +:Revision: $Revision: 1.8 $ + +:abstract: + lighttpd a secure, fast, compliant and very flexible web-server + which has been optimized for high-performance environments. It has a very + low memory footprint compared to other webservers and takes care of cpu-load. + Its advanced feature-set (FastCGI, CGI, Auth, Output-Compression, + URL-Rewriting and many more) make lighttpd the perfect webserver-software + for every server that is suffering load problems. + +the naming +---------- + +lighttpd is a __httpd__ which is + +- fast as __light__ning and +- __light__ when it comes to memory consumption and system requirements + +Features +-------- + +Network +``````` + +- IPv4, IPv6 + +Protocols +````````` + +- HTTP/1.0 (http://www.ietf.org/rfc/rfc1945.txt) +- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt) +- HTTPS (provided by openssl) +- CGI/1.1 (http://CGI-Spec.Golux.Com/) +- FastCGI (http://www.fastcgi.com/devkit/doc/fcgi-spec.html) + +Advanced Features +````````````````` + +- load-balanced FastCGI + (one webserver distributes requests to multiple PHP-servers via FastCGI) +- custom error pages (for Response-Code 400-599) +- virtual hosts +- directory listings +- streaming CGI and FastCGI +- URL-Rewriting +- HTTP-Redirection +- output-compression with transparent caching + +FastCGI-Support +``````````````` + +- parses the Response-header and completes the HTTP-header accordingly +- Keep-Alive handling based on Content-Length header + +PHP-Support +``````````` + +- same speed as or faster than apache + mod_php4 +- handles various PHP bugs in the FastCGI SAPI +- includes a utility to spawn FastCGI processes (necessary for PHP 4.3.x) + +Security features +````````````````` + +- chroot(), set UID, set GID +- protecting docroot + +HTTP/1.1 features +````````````````` + +- Ranges (start-end, start-, -end, multiple ranges) +- HTTP/1.0 Keep-Alive + HTTP/1.1 persistent Connections +- methods: GET, HEAD, POST +- Last-Modified + If-Modified handling +- sends Content-Length if possible +- sends Transfer-Encoding: chunk, if Content-Length is not possible +- sends Content-Type +- on-the-fly output compression (deflate, gzip) +- authentication: basic and digest + (http://www.ietf.org/rfc/rfc2617.txt) + +HTTP/1.1 compliance +``````````````````` + +- Sends 206 for Range Requests +- Sends 304 for If-Modified Requests +- Sends 400 for missing Host on HTTP/1.1 requests +- Sends 400 for broken Request-Line +- Sends 411 for missing Content-Length on POST requests +- Sends 416 for "out-of-range" on Range: Header +- Sends 501 for request-method != (GET|POST|HEAD) +- Sends 505 for protocol != HTTP/1.0 or HTTP/1.1 +- Sends Date: on every requests + +Intended Audience +----------------- + +- Ad-Server Front-Ends ("Banner-Schleuder") + - delivering small files rapidly +- php-servers under high load + (load-balancing the php-request over multiple PHP-servers) + +Works with +---------- + +It has been tested to work with + +- IE 6.0 +- Mozilla 1.x +- Konqueror 3.1 + (for Keep-Alive/Persistent Connections, Accept-Encoding for PHP + gzip) +- wget + (for Resuming) +- acrobat plugin + (for multiple ranges) + + +Works on +-------- + +lighttpd has been verified to compile and work on + +- Linux +- FreeBSD +- NetBSD +- Solaris 8 + 9 +- SGI IRIX 6.5 + +missing for HTTP/1.1 compliance +------------------------------- +- parsing chunked POST request + +----------------- +Starting lighttpd +----------------- + +As daemon in the background: :: + + $ lighttpd -f <configfile> + +or without detaching from the console: :: + + $ lighttpd -D -f <configfile> + + diff --git a/config.h.in b/config.h.in new file mode 100644 index 00000000..19b5d7c4 --- /dev/null +++ b/config.h.in @@ -0,0 +1,374 @@ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +#undef HAVE_ARPA_INET_H + +/* Define to 1 if you have the <attr/attributes.h> header file. */ +#undef HAVE_ATTR_ATTRIBUTES_H + +/* Define to 1 if you have the <bzlib.h> header file. */ +#undef HAVE_BZLIB_H + +/* Define to 1 if you have the `chroot' function. */ +#undef HAVE_CHROOT + +/* Define to 1 if you have the <crypt.h> header file. */ +#undef HAVE_CRYPT_H + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the `dup2' function. */ +#undef HAVE_DUP2 + +/* Define to 1 if you have the `epoll_ctl' function. */ +#undef HAVE_EPOLL_CTL + +/* Define to 1 if you have the <errmsg.h> header file. */ +#undef HAVE_ERRMSG_H + +/* Define to 1 if you have the <fcntl.h> header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `fork' function. */ +#undef HAVE_FORK + +/* Define to 1 if you have the `getcwd' function. */ +#undef HAVE_GETCWD + +/* Define to 1 if you have the `gethostbyname' function. */ +#undef HAVE_GETHOSTBYNAME + +/* Define to 1 if you have the `getopt' function. */ +#undef HAVE_GETOPT + +/* Define to 1 if you have the <getopt.h> header file. */ +#undef HAVE_GETOPT_H + +/* Define to 1 if you have the `getrlimit' function. */ +#undef HAVE_GETRLIMIT + +/* Define to 1 if you have the `getuid' function. */ +#undef HAVE_GETUID + +/* Define to 1 if you have the `inet_ntoa' function. */ +#undef HAVE_INET_NTOA + +/* Define to 1 if you have the `inet_ntop' function. */ +#undef HAVE_INET_NTOP + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Whether to enable IPv6 support */ +#undef HAVE_IPV6 + +/* Define to 1 if you have the `kqueue' function. */ +#undef HAVE_KQUEUE + +/* Define to 1 if you have the <lber.h> header file. */ +#undef HAVE_LBER_H + +/* Define to 1 if you have the <ldap.h> header file. */ +#undef HAVE_LDAP_H + +/* libbz2 */ +#undef HAVE_LIBBZ2 + +/* libcrypt */ +#undef HAVE_LIBCRYPT + +/* libdl */ +#undef HAVE_LIBDL + +/* liblber */ +#undef HAVE_LIBLBER + +/* libldap */ +#undef HAVE_LIBLDAP + +/* libpcre */ +#undef HAVE_LIBPCRE + +/* Have libssl */ +#undef HAVE_LIBSSL + +/* libz */ +#undef HAVE_LIBZ + +/* Define to 1 if you have the `localtime_r' function. */ +#undef HAVE_LOCALTIME_R + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have the `mmap' function. */ +#undef HAVE_MMAP + +/* Define to 1 if you have the `munmap' function. */ +#undef HAVE_MUNMAP + +/* mysql support */ +#undef HAVE_MYSQL + +/* Define to 1 if you have the <mysql.h> header file. */ +#undef HAVE_MYSQL_H + +/* Define to 1 if you have the <netinet/in.h> header file. */ +#undef HAVE_NETINET_IN_H + +/* Define to 1 if you have the <openssl/ssl.h> header file. */ +#undef HAVE_OPENSSL_SSL_H + +/* Define to 1 if you have the <pcre.h> header file. */ +#undef HAVE_PCRE_H + +/* Define to 1 if you have the `poll' function. */ +#undef HAVE_POLL + +/* Define to 1 if you have the <poll.h> header file. */ +#undef HAVE_POLL_H + +/* Define to 1 if you have the `port_create' function. */ +#undef HAVE_PORT_CREATE + +/* Define to 1 if you have the <pwd.h> header file. */ +#undef HAVE_PWD_H + +/* Define to 1 if you have the `select' function. */ +#undef HAVE_SELECT + +/* Define to 1 if you have the `sendfile' function. */ +#undef HAVE_SENDFILE + +/* Define to 1 if you have the `sendfile64' function. */ +#undef HAVE_SENDFILE64 + +/* solaris sendfilev */ +#undef HAVE_SENDFILEV + +/* broken sendfile */ +#undef HAVE_SENDFILE_BROKEN + +/* Define to 1 if you have the `send_file' function. */ +#undef HAVE_SEND_FILE + +/* Define to 1 if you have the `sigaction' function. */ +#undef HAVE_SIGACTION + +/* Define to 1 if you have the `signal' function. */ +#undef HAVE_SIGNAL + +/* Define to 1 if you have the `sigtimedwait' function. */ +#undef HAVE_SIGTIMEDWAIT + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if the system has the type `socklen_t'. */ +#undef HAVE_SOCKLEN_T + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +#undef HAVE_STAT_EMPTY_STRING_BUG + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the `strftime' function. */ +#undef HAVE_STRFTIME + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strstr' function. */ +#undef HAVE_STRSTR + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if the system has the type `struct sockaddr_storage'. */ +#undef HAVE_STRUCT_SOCKADDR_STORAGE + +/* gmtoff in struct tm */ +#undef HAVE_STRUCT_TM_GMTOFF + +/* Define to 1 if you have the <syslog.h> header file. */ +#undef HAVE_SYSLOG_H + +/* Define to 1 if you have the <sys/devpoll.h> header file. */ +#undef HAVE_SYS_DEVPOLL_H + +/* Define to 1 if you have the <sys/epoll.h> header file. */ +#undef HAVE_SYS_EPOLL_H + +/* Define to 1 if you have the <sys/event.h> header file. */ +#undef HAVE_SYS_EVENT_H + +/* Define to 1 if you have the <sys/filio.h> header file. */ +#undef HAVE_SYS_FILIO_H + +/* Define to 1 if you have the <sys/mman.h> header file. */ +#undef HAVE_SYS_MMAN_H + +/* Define to 1 if you have the <sys/poll.h> header file. */ +#undef HAVE_SYS_POLL_H + +/* Define to 1 if you have the <sys/port.h> header file. */ +#undef HAVE_SYS_PORT_H + +/* Define to 1 if you have the <sys/resource.h> header file. */ +#undef HAVE_SYS_RESOURCE_H + +/* Define to 1 if you have the <sys/select.h> header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the <sys/sendfile.h> header file. */ +#undef HAVE_SYS_SENDFILE_H + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <sys/uio.h> header file. */ +#undef HAVE_SYS_UIO_H + +/* Define to 1 if you have the <sys/un.h> header file. */ +#undef HAVE_SYS_UN_H + +/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */ +#undef HAVE_SYS_WAIT_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the <valgrind/valgrind.h> header file. */ +#undef HAVE_VALGRIND_VALGRIND_H + +/* Define to 1 if you have the `vfork' function. */ +#undef HAVE_VFORK + +/* Define to 1 if you have the <vfork.h> header file. */ +#undef HAVE_VFORK_H + +/* Define to 1 if you have the <winsock2.h> header file. */ +#undef HAVE_WINSOCK2_H + +/* Define to 1 if `fork' works. */ +#undef HAVE_WORKING_FORK + +/* Define to 1 if `vfork' works. */ +#undef HAVE_WORKING_VFORK + +/* Define to 1 if you have the `writev' function. */ +#undef HAVE_WRITEV + +/* libattr */ +#undef HAVE_XATTR + +/* Define to 1 if you have the <zlib.h> header file. */ +#undef HAVE_ZLIB_H + +/* lighttpd-version-id */ +#undef LIGHTTPD_VERSION_ID + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +#undef LSTAT_FOLLOWS_SLASHED_SYMLINK + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define if compiler has function prototypes */ +#undef PROTOTYPES + +/* Define as the return type of signal handlers (`int' or `void'). */ +#undef RETSIGTYPE + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Define to 1 if on AIX 3. + System headers sometimes define this. + We just want to avoid a redefinition error message. */ +#ifndef _ALL_SOURCE +# undef _ALL_SOURCE +#endif + +/* Define to 1 if on MINIX. */ +#undef _MINIX + +/* Define to 2 if the system does not provide POSIX.1 features except with + this defined. */ +#undef _POSIX_1_SOURCE + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +#undef _POSIX_SOURCE + +/* Define to 1 if type `char' is unsigned and you are not using gcc. */ +#ifndef __CHAR_UNSIGNED__ +# undef __CHAR_UNSIGNED__ +#endif + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define as `__inline' if that's what the C compiler calls it, or to nothing + if it is not supported. */ +#undef inline + +/* Define to `long' if <sys/types.h> does not define. */ +#undef off_t + +/* Define to `int' if <sys/types.h> does not define. */ +#undef pid_t + +/* Define to `unsigned' if <sys/types.h> does not define. */ +#undef size_t + +/* Define as `fork' if `vfork' does not work. */ +#undef vfork diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..1e13a03a --- /dev/null +++ b/configure.in @@ -0,0 +1,421 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. +AC_PREREQ(2.57) +AC_INIT(lighttpd, 1.3.11, jan@kneschke.de) +AC_CONFIG_SRCDIR([src/server.c]) + +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE + +AM_CONFIG_HEADER([config.h]) + +AM_MAINTAINER_MODE + +# Checks for programs. +AC_PROG_CC +AC_PROG_LD +AC_PROG_INSTALL +AC_PROG_AWK +AC_PROG_CPP +dnl AC_PROG_CXX +AC_PROG_LN_S +AC_PROG_MAKE_SET + +dnl check environment +AC_AIX +AC_ISC_POSIX +AC_MINIX + +dnl AC_CANONICAL_HOST +case $host_os in + *darwin*|*cygwin*|*aix*|*mingw* ) NO_RDYNAMIC=yes;; + * ) NO_RDYNAMIC=no;; +esac +AM_CONDITIONAL(NO_RDYNAMIC, test x$NO_RDYNAMIC = xyes) + +AC_EXEEXT + +dnl more automake stuff +AM_C_PROTOTYPES + +dnl libtool +AC_DISABLE_STATIC +AC_ENABLE_SHARED + +AC_LIBTOOL_DLOPEN +AC_PROG_LIBTOOL + +dnl for solaris and localtime_r +CPPFLAGS="${CPPFLAGS} -D_REENTRANT" + +# Checks for header files. +AC_HEADER_STDC +AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netinet/in.h stdlib.h string.h \ +sys/socket.h sys/time.h unistd.h sys/sendfile.h sys/uio.h \ +getopt.h sys/epoll.h sys/select.h poll.h sys/poll.h sys/devpoll.h sys/filio.h \ +sys/mman.h sys/event.h sys/port.h winsock2.h pwd.h \ +sys/resource.h sys/un.h syslog.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_C_CHAR_UNSIGNED +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T + +AC_CHECK_MEMBER(struct tm.tm_gmtoff,AC_DEFINE([HAVE_STRUCT_TM_GMTOFF],[1],[gmtoff in struct tm]),,[#include <time.h>]) +AC_CHECK_TYPES(struct sockaddr_storage,,,[#include <sys/socket.h>]) +AC_CHECK_TYPES(socklen_t,,,[#include <sys/types.h> +#include <sys/socket.h>]) + +# Checks for library functions. +AC_FUNC_FORK +dnl AC_FUNC_MALLOC +#AC_FUNC_MMAP +dnl AC_FUNC_REALLOC +AC_TYPE_SIGNAL +AC_FUNC_STAT +AC_FUNC_STRFTIME + +dnl Checks for database. +MYSQL_INCLUDE="" + +AC_PATH_PROG(MYSQLCONFIG, mysql_config) +AC_MSG_CHECKING(for MySQL support) +AC_ARG_WITH(mysql, + AC_HELP_STRING([--with-mysql@<:@=PATH@:>@],[Include MySQL support. PATH is the path to 'mysql_config']), + [ + if test "$withval" != "no"; then + if test "$withval" = "yes"; then + withval=$MYSQLCONFIG + fi + + if test \! -x $withval; then + echo "--with-mysql=path-to-mysql_config" + fi + MYSQL_INCLUDE="`$withval --cflags | sed s/\'//g`" + MYSQL_LIBS="`$withval --libs | sed s/\'//g`" + + AC_MSG_RESULT(yes) + + AC_MSG_CHECKING(for MySQL includes at) + AC_MSG_RESULT($MYSQL_INCLUDE) + + AC_MSG_CHECKING(for MySQL libraries at) + AC_MSG_RESULT($MYSQL_LIBS) + dnl check for errmsg.h, which isn't installed by some versions of 3.21 + old_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $MYSQL_INCLUDE" + AC_CHECK_HEADERS(errmsg.h mysql.h) + CPPFLAGS="$old_CPPFLAGS" + + AC_DEFINE([HAVE_MYSQL], [1], [mysql support]) + else + AC_MSG_RESULT(no) + fi +],[AC_MSG_RESULT(no)]) + +AC_SUBST(MYSQL_LIBS) +AC_SUBST(MYSQL_INCLUDE) + + +AC_MSG_CHECKING(for LDAP support) +AC_ARG_WITH(ldap, AC_HELP_STRING([--with-ldap],[enable LDAP support]), +[AC_MSG_RESULT(yes) + AC_CHECK_LIB(ldap, ldap_bind, [ + AC_CHECK_HEADERS([ldap.h],[ + LDAP_LIB=-lldap + AC_DEFINE([HAVE_LIBLDAP], [1], [libldap]) + AC_DEFINE([HAVE_LDAP_H], [1]) + ]) + ]) + AC_SUBST(LDAP_LIB) + AC_CHECK_LIB(lber, ber_printf, [ + AC_CHECK_HEADERS([lber.h],[ + LBER_LIB=-llber + AC_DEFINE([HAVE_LIBLBER], [1], [liblber]) + AC_DEFINE([HAVE_LBER_H], [1]) + ]) + ]) + AC_SUBST(LBER_LIB) + +],[AC_MSG_RESULT(no)]) + +AC_MSG_CHECKING(for extended attributes support) +AC_ARG_WITH(attr, AC_HELP_STRING([--with-attr],[enable extended attribute support]), +[AC_MSG_RESULT(yes) + AC_CHECK_LIB(attr, attr_get, [ + AC_CHECK_HEADERS([attr/attributes.h],[ + ATTR_LIB=-lattr + AC_DEFINE([HAVE_XATTR], [1], [libattr]) + AC_DEFINE([HAVE_ATTR_ATTRIBUTES_H], [1]) + ]) + ]) +],[AC_MSG_RESULT(no)]) +AC_SUBST(ATTR_LIB) + +AC_MSG_CHECKING(for valgrind) +AC_ARG_WITH(valgrind, AC_HELP_STRING([--with-valgrind],[enable internal +support for valgrind]), +[AC_MSG_RESULT(yes) + AC_CHECK_HEADERS([valgrind/valgrind.h]) +],[AC_MSG_RESULT(no)]) + +AC_MSG_CHECKING(for OpenSSL) +dnl check for openssl + AC_ARG_WITH(openssl, + AC_HELP_STRING([--with-openssl@<:@=DIR@:>@],[Include openssl support (default no)]), +[ + if test "$withval" != "no"; then + if test "$withval" != "yes"; then + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib" + fi + + use_openssl=yes + AC_MSG_RESULT($withval) + else + use_openssl=no + AC_MSG_RESULT(no) + fi +],[ + use_openssl=no + AC_MSG_RESULT(no) +]) + +AC_ARG_WITH(openssl-includes, + AC_HELP_STRING([--with-openssl-includes=DIR],[OpenSSL includes]), + [ use_openssl=yes CPPFLAGS="$CPPFLAGS -I$withval" ] +) + +AC_ARG_WITH(openssl-libs, + AC_HELP_STRING([--with-openssl-libs=DIR],[OpenSSL libraries]), + [ use_openssl=yes LDFLAGS="$LDFLAGS -L$withval" ] +) + +if test "x$use_openssl" = "xyes"; then + AC_CHECK_HEADERS([openssl/ssl.h]) + OLDLIBS="$LIBS" + AC_CHECK_LIB(crypto, BIO_f_base64, [ + AC_CHECK_LIB(ssl, SSL_new, [ SSL_LIB="-lssl -lcrypto" + AC_DEFINE(HAVE_LIBSSL, [], [Have libssl]) ], [], [ -lcrypto ]) + ], [], []) + LIBS="$OLDLIBS" + AC_SUBST(SSL_LIB) +fi + +if test "x$cross_compiling" = xno; then + AC_PATH_PROG(PCRECONFIG, pcre-config) + + if test x"$PCRECONFIG" != x; then + PCRE_LIB=`$PCRECONFIG --libs` + CPPFLAGS="$CPPFLAGS `$PCRECONFIG --cflags`" + OLDLIBS="$LIBS" + LIBS="$LIBS $PCRE_LIB" + AC_CHECK_LIB(pcre, pcre_compile, [ + AC_CHECK_HEADERS([pcre.h], [ + AC_DEFINE([HAVE_LIBPCRE], [1], [libpcre]) + AC_DEFINE([HAVE_PCRE_H], [1]) + ]) + ]) + LIBS="$OLDLIBS" + fi +fi + +AC_SUBST(PCRE_LIB) + +AC_CHECK_LIB(z, deflate, [ + AC_CHECK_HEADERS([zlib.h],[ + Z_LIB=-lz + AC_DEFINE([HAVE_LIBZ], [1], [libz]) + AC_DEFINE([HAVE_ZLIB_H], [1]) + ]) +]) +AC_SUBST(Z_LIB) + +AC_CHECK_LIB(bz2, BZ2_bzCompress, [ + AC_CHECK_HEADERS([bzlib.h],[ + BZ_LIB=-lbz2 + AC_DEFINE([HAVE_LIBBZ2], [1], [libbz2]) + AC_DEFINE([HAVE_BZLIB_H], [1]) + ]) +]) +AC_SUBST(BZ_LIB) + +AC_SEARCH_LIBS(socket,socket) +AC_SEARCH_LIBS(gethostbyname,nsl socket) +AC_SEARCH_LIBS(hstrerror,resolv) + +save_LIBS=$LIBS +AC_SEARCH_LIBS(dlopen,dl,[ + AC_CHECK_HEADERS([dlfcn.h],[ + if test "$ac_cv_search_dlopen" != no; then + test "$ac_cv_search_dlopen" = "none required" || DL_LIB="$ac_cv_search_dlopen" + fi + + AC_DEFINE([HAVE_LIBDL], [1], [libdl]) + AC_DEFINE([HAVE_DLFCN_H], [1]) + ]) +]) +LIBS=$save_LIBS +AC_SUBST(DL_LIB) + +save_LIBS=$LIBS +AC_SEARCH_LIBS(crypt,crypt,[ + AC_CHECK_HEADERS([crypt.h],[ + AC_DEFINE([HAVE_CRYPT_H], [1]) + ]) + + AC_DEFINE([HAVE_LIBCRYPT], [1], [libcrypt]) + if test "$ac_cv_search_crypt" != no; then + test "$ac_cv_search_crypt" = "none required" || CRYPT_LIB="$ac_cv_search_crypt" + fi +]) +LIBS=$save_LIBS +AC_SUBST(CRYPT_LIB) + +save_LIBS=$LIBS +AC_SEARCH_LIBS(sendfilev,sendfile,[ + if test "$ac_cv_search_sendfilev" != no; then + test "$ac_cv_search_sendfilev" = "none required" || SENDFILE_LIB="$ac_cv_search_sendfilev" + AC_DEFINE([HAVE_SENDFILEV], [1], [solaris sendfilev]) + fi +]) +LIBS=$save_LIBS +AC_SUBST(SENDFILE_LIB) + +case $host_os in + *mingw* ) LIBS="$LIBS -lwsock32";; + * ) ;; +esac + +AC_CHECK_FUNCS([dup2 getcwd inet_ntoa inet_ntop memset mmap munmap strchr \ + strdup strerror strstr strtol sendfile getopt socket \ + gethostbyname poll sigtimedwait epoll_ctl getrlimit chroot \ + getuid select signal\ + writev sigaction sendfile64 send_file kqueue port_create localtime_r]) + +if test "x$ac_cv_func_sendfile" = xyes; then + # check if sendfile works + AC_MSG_CHECKING(if sendfile works) + if test "x$cross_compiling" = xno; then + AC_TRY_RUN([ + #include <errno.h> + int main() { + int o = 0; + if (-1 == sendfile(0, 0, &o, 0) && errno == ENOSYS) return -1; + return 0; + } ], + AC_MSG_RESULT(yes), + [ AC_MSG_RESULT(no) + AC_DEFINE([HAVE_SENDFILE_BROKEN], [1], [broken sendfile]) ] ) + else + AC_MSG_RESULT(no, cross-compiling) + AC_DEFINE([HAVE_SENDFILE_BROKEN], [1], [broken sendfile]) + fi +fi + +dnl Check for IPv6 support + +AC_ARG_ENABLE(ipv6, + AC_HELP_STRING([--disable-ipv6],[disable IPv6 support]), + [case "${enableval}" in + yes) ipv6=true ;; + no) ipv6=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-ipv6) ;; + esac],[ipv6=true]) + +if test x$ipv6 = xtrue; then + AC_CACHE_CHECK([for IPv6 support], ac_cv_ipv6_support, + [AC_TRY_LINK([ #include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h>], [struct sockaddr_in6 s; struct in6_addr t=in6addr_any; int i=AF_INET6; s; t.s6_addr[0] = 0; ], + [ac_cv_ipv6_support=yes], [ac_cv_ipv6_support=no])]) + + if test "$ac_cv_ipv6_support" = yes; then + AC_DEFINE(HAVE_IPV6,1,[Whether to enable IPv6 support]) + fi +fi + + +AC_MSG_CHECKING(for Large File System support) +AC_ARG_ENABLE(lfs, + AC_HELP_STRING([--enable-lfs],[Turn on Large File System (default)]), + [case "${enableval}" in + yes) CPPFLAGS="${CPPFLAGS} -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES" ;; + no) ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-lfs) ;; + esac],[CPPFLAGS="${CPPFLAGS} -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES"]) +AC_MSG_RESULT($enableval) + +AM_CONDITIONAL(CROSS_COMPILING, test "x$cross_compiling" = xyes) + +if test "${GCC}" = "yes"; then + CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic" +fi + +dnl build version-id +LIGHTTPD_VERSION_ID=`echo $PACKAGE_VERSION | $AWK -F '.' '{print "(" $1 " << 16 | " $2 " << 8 | " $3 ")"}'` +AC_DEFINE_UNQUOTED([LIGHTTPD_VERSION_ID], [$LIGHTTPD_VERSION_ID], [lighttpd-version-id]) + +AC_CONFIG_FILES([Makefile debian/Makefile src/Makefile doc/Makefile tests/Makefile \ + tests/docroot/Makefile \ + tests/docroot/123/Makefile \ + tests/docroot/www/Makefile \ + tests/docroot/www/go/Makefile \ + tests/docroot/www/indexfile/Makefile \ + lighttpd.spec distribute.sh cygwin/Makefile cygwin/lighttpd.README + openwrt/Makefile openwrt/control openwrt/lighttpd.mk]) +AC_OUTPUT + +$ECHO +$ECHO "Plugins:" +$ECHO +if test ! "x$PCRE_LIB" = x; then + $ECHO "mod_rewrite : enabled" + $ECHO "mod_redirect : enabled" + $ECHO "mod_ssi : enabled" +else + $ECHO "mod_rewrite : disabled (libpcre missing)" + $ECHO "mod_redirect : disabled (libpcre missing)" + $ECHO "mod_ssi : disabled (libpcre missing)" +fi + +$ECHO "mod_cgi : enabled" +$ECHO "mod_fastcgi : enabled" +$ECHO "mod_proxy : enabled" +$ECHO "mod_evhost : enabled" +$ECHO "mod_simple_vhost: enabled" + +if test "x$MYSQL_LIBS" = x; then + $ECHO "mod_mysql_vhost : disabled (libmysqlclient missing or mysql support disabled)" +else + $ECHO "mod_mysql_vhost : enabled" +fi + +$ECHO "mod_access : enabled" +$ECHO "mod_alias : enabled" +$ECHO "mod_setenv : enabled" +$ECHO "mod_usertrack : enabled" +if test "x$Z_LIB" = x; then + $ECHO "mod_compress : disabled (libz missing)" +else + $ECHO "mod_compress : enabled" +fi + +# no crypt call +if test "$ac_cv_search_crypt" = no; then + $ECHO "mod_auth : enabled, crypt() support disabled" +else + $ECHO "mod_auth : enabled" +fi +$ECHO "mod_status : enabled" +$ECHO "mod_accesslog : enabled" +$ECHO "mod_rrdtool : enabled" +$ECHO "mod_secdownload : enabled" +$ECHO "mod_expire : enabled" + +$ECHO diff --git a/cygwin/.cvsignore b/cygwin/.cvsignore new file mode 100644 index 00000000..3dda7298 --- /dev/null +++ b/cygwin/.cvsignore @@ -0,0 +1,2 @@ +Makefile.in +Makefile diff --git a/cygwin/Makefile.am b/cygwin/Makefile.am new file mode 100644 index 00000000..8892f643 --- /dev/null +++ b/cygwin/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST=lighttpd.README setup.hint diff --git a/cygwin/lighttpd.README b/cygwin/lighttpd.README new file mode 100644 index 00000000..5873d01e --- /dev/null +++ b/cygwin/lighttpd.README @@ -0,0 +1,114 @@ +lighttpd
+------------------------------------------
+A fast, secure and flexible webserver
+
+Runtime requirements:
+ cygwin-1.5.10 or newer
+ crypt-1.1 or newer
+ libbz2_1-1.0.2 or newer
+ libpcre0-4.5 or newer
+ openssl-0.9.7d or newer
+ zlib-1.2.1 or newer
+
+Build requirements:
+ cygwin-1.5.10 or newer
+ gcc-3.3.1-3 or newer
+ binutils-20030901-1 or newer
+ crypt
+ openssl-devel
+ openssl
+ openldap
+ openldap-devel
+ zlib
+ bzip2
+
+Canonical homepage:
+ http://jan.kneschke.de/projects/lighttpd/
+
+Canonical download:
+ http://jan.kneschke.de/projects/lighttpd/download
+
+------------------------------------
+
+Build instructions:
+ unpack lighttpd-1.3.11-<REL>-src.tar.bz2
+ if you use setup to install this src package, it will be
+ unpacked under /usr/src automatically
+ cd /usr/src
+ ./lighttpd-1.3.11-<REL>.sh all
+
+This will create:
+ /usr/src/lighttpd-1.3.11-<REL>.tar.bz2
+ /usr/src/lighttpd-1.3.11-<REL>-src.tar.bz2
+
+Or use './lighttpd-1.3.11-<REL>.sh prep' to get a patched source directory
+
+-------------------------------------------
+
+Files included in the binary distribution:
+
+ /etc/lighttpd/lighttpd.conf.default
+ /usr/lib/cyglightcomp.dll
+ /usr/lib/lighttpd/mod_access.dll
+ /usr/lib/lighttpd/mod_accesslog.dll
+ /usr/lib/lighttpd/mod_auth.dll
+ /usr/lib/lighttpd/mod_cgi.dll
+ /usr/lib/lighttpd/mod_compress.dll
+ /usr/lib/lighttpd/mod_evhost.dll
+ /usr/lib/lighttpd/mod_expire.dll
+ /usr/lib/lighttpd/mod_fastcgi.dll
+ /usr/lib/lighttpd/mod_httptls.dll
+ /usr/lib/lighttpd/mod_maps.dll
+ /usr/lib/lighttpd/mod_proxy.dll
+ /usr/lib/lighttpd/mod_redirect.dll
+ /usr/lib/lighttpd/mod_rewrite.dll
+ /usr/lib/lighttpd/mod_rrdtool.dll
+ /usr/lib/lighttpd/mod_secdownload.dll
+ /usr/lib/lighttpd/mod_simple_vhost.dll
+ /usr/lib/lighttpd/mod_ssi.dll
+ /usr/lib/lighttpd/mod_status.dll
+ /usr/lib/lighttpd/mod_usertrack.dll
+ /usr/sbin/lighttpd.exe
+ /usr/share/doc/Cygwin/lighttpd-1.3.0.README
+ /usr/share/doc/lighttpd-1.3.0/accesslog.txt
+ /usr/share/doc/lighttpd-1.3.0/authentification.txt
+ /usr/share/doc/lighttpd-1.3.0/AUTHORS
+ /usr/share/doc/lighttpd-1.3.0/cgi.txt
+ /usr/share/doc/lighttpd-1.3.0/ChangeLog
+ /usr/share/doc/lighttpd-1.3.0/compress.txt
+ /usr/share/doc/lighttpd-1.3.0/configuration.txt
+ /usr/share/doc/lighttpd-1.3.0/COPYING
+ /usr/share/doc/lighttpd-1.3.0/fastcgi-state.txt
+ /usr/share/doc/lighttpd-1.3.0/fastcgi.txt
+ /usr/share/doc/lighttpd-1.3.0/features.txt
+ /usr/share/doc/lighttpd-1.3.0/INSTALL
+ /usr/share/doc/lighttpd-1.3.0/NEWS
+ /usr/share/doc/lighttpd-1.3.0/performance.txt
+ /usr/share/doc/lighttpd-1.3.0/plugins.txt
+ /usr/share/doc/lighttpd-1.3.0/proxy.txt
+ /usr/share/doc/lighttpd-1.3.0/README
+ /usr/share/doc/lighttpd-1.3.0/redirect.txt
+ /usr/share/doc/lighttpd-1.3.0/rewrite.txt
+ /usr/share/doc/lighttpd-1.3.0/rrdtool.txt
+ /usr/share/doc/lighttpd-1.3.0/secdownload.txt
+ /usr/share/doc/lighttpd-1.3.0/security.txt
+ /usr/share/doc/lighttpd-1.3.0/simple-vhost.txt
+ /usr/share/doc/lighttpd-1.3.0/skeleton.txt
+ /usr/share/doc/lighttpd-1.3.0/ssi.txt
+ /usr/share/doc/lighttpd-1.3.0/state.txt
+ /usr/share/man/man1/lighttpd.1.gz
+
+------------------
+
+Port Notes:
+
+---------- lighttpd-1.3.1-1 -----------
+
+Updated to 1.3.1
+
+---------- lighttpd-1.3.0-1 -----------
+Initial release
+
+Cygwin port maintained by: Jan Kneschke <jan@kneschke.de>
+Please address all questions to the Cygwin mailing list at <cygwin@cygwin.com>
+
diff --git a/cygwin/lighttpd.README.in b/cygwin/lighttpd.README.in new file mode 100644 index 00000000..2b455462 --- /dev/null +++ b/cygwin/lighttpd.README.in @@ -0,0 +1,114 @@ +lighttpd
+------------------------------------------
+A fast, secure and flexible webserver
+
+Runtime requirements:
+ cygwin-1.5.10 or newer
+ crypt-1.1 or newer
+ libbz2_1-1.0.2 or newer
+ libpcre0-4.5 or newer
+ openssl-0.9.7d or newer
+ zlib-1.2.1 or newer
+
+Build requirements:
+ cygwin-1.5.10 or newer
+ gcc-3.3.1-3 or newer
+ binutils-20030901-1 or newer
+ crypt
+ openssl-devel
+ openssl
+ openldap
+ openldap-devel
+ zlib
+ bzip2
+
+Canonical homepage:
+ http://jan.kneschke.de/projects/lighttpd/
+
+Canonical download:
+ http://jan.kneschke.de/projects/lighttpd/download
+
+------------------------------------
+
+Build instructions:
+ unpack lighttpd-@VERSION@-<REL>-src.tar.bz2
+ if you use setup to install this src package, it will be
+ unpacked under /usr/src automatically
+ cd /usr/src
+ ./lighttpd-@VERSION@-<REL>.sh all
+
+This will create:
+ /usr/src/lighttpd-@VERSION@-<REL>.tar.bz2
+ /usr/src/lighttpd-@VERSION@-<REL>-src.tar.bz2
+
+Or use './lighttpd-@VERSION@-<REL>.sh prep' to get a patched source directory
+
+-------------------------------------------
+
+Files included in the binary distribution:
+
+ /etc/lighttpd/lighttpd.conf.default
+ /usr/lib/cyglightcomp.dll
+ /usr/lib/lighttpd/mod_access.dll
+ /usr/lib/lighttpd/mod_accesslog.dll
+ /usr/lib/lighttpd/mod_auth.dll
+ /usr/lib/lighttpd/mod_cgi.dll
+ /usr/lib/lighttpd/mod_compress.dll
+ /usr/lib/lighttpd/mod_evhost.dll
+ /usr/lib/lighttpd/mod_expire.dll
+ /usr/lib/lighttpd/mod_fastcgi.dll
+ /usr/lib/lighttpd/mod_httptls.dll
+ /usr/lib/lighttpd/mod_maps.dll
+ /usr/lib/lighttpd/mod_proxy.dll
+ /usr/lib/lighttpd/mod_redirect.dll
+ /usr/lib/lighttpd/mod_rewrite.dll
+ /usr/lib/lighttpd/mod_rrdtool.dll
+ /usr/lib/lighttpd/mod_secdownload.dll
+ /usr/lib/lighttpd/mod_simple_vhost.dll
+ /usr/lib/lighttpd/mod_ssi.dll
+ /usr/lib/lighttpd/mod_status.dll
+ /usr/lib/lighttpd/mod_usertrack.dll
+ /usr/sbin/lighttpd.exe
+ /usr/share/doc/Cygwin/lighttpd-1.3.0.README
+ /usr/share/doc/lighttpd-1.3.0/accesslog.txt
+ /usr/share/doc/lighttpd-1.3.0/authentification.txt
+ /usr/share/doc/lighttpd-1.3.0/AUTHORS
+ /usr/share/doc/lighttpd-1.3.0/cgi.txt
+ /usr/share/doc/lighttpd-1.3.0/ChangeLog
+ /usr/share/doc/lighttpd-1.3.0/compress.txt
+ /usr/share/doc/lighttpd-1.3.0/configuration.txt
+ /usr/share/doc/lighttpd-1.3.0/COPYING
+ /usr/share/doc/lighttpd-1.3.0/fastcgi-state.txt
+ /usr/share/doc/lighttpd-1.3.0/fastcgi.txt
+ /usr/share/doc/lighttpd-1.3.0/features.txt
+ /usr/share/doc/lighttpd-1.3.0/INSTALL
+ /usr/share/doc/lighttpd-1.3.0/NEWS
+ /usr/share/doc/lighttpd-1.3.0/performance.txt
+ /usr/share/doc/lighttpd-1.3.0/plugins.txt
+ /usr/share/doc/lighttpd-1.3.0/proxy.txt
+ /usr/share/doc/lighttpd-1.3.0/README
+ /usr/share/doc/lighttpd-1.3.0/redirect.txt
+ /usr/share/doc/lighttpd-1.3.0/rewrite.txt
+ /usr/share/doc/lighttpd-1.3.0/rrdtool.txt
+ /usr/share/doc/lighttpd-1.3.0/secdownload.txt
+ /usr/share/doc/lighttpd-1.3.0/security.txt
+ /usr/share/doc/lighttpd-1.3.0/simple-vhost.txt
+ /usr/share/doc/lighttpd-1.3.0/skeleton.txt
+ /usr/share/doc/lighttpd-1.3.0/ssi.txt
+ /usr/share/doc/lighttpd-1.3.0/state.txt
+ /usr/share/man/man1/lighttpd.1.gz
+
+------------------
+
+Port Notes:
+
+---------- lighttpd-1.3.1-1 -----------
+
+Updated to 1.3.1
+
+---------- lighttpd-1.3.0-1 -----------
+Initial release
+
+Cygwin port maintained by: Jan Kneschke <jan@kneschke.de>
+Please address all questions to the Cygwin mailing list at <cygwin@cygwin.com>
+
diff --git a/cygwin/setup.hint b/cygwin/setup.hint new file mode 100644 index 00000000..c50b615c --- /dev/null +++ b/cygwin/setup.hint @@ -0,0 +1,4 @@ +sdesc: "a light-weight and flexible webserver" +ldesc: "lighttpd a secure, fast, compliant and very flexible web-server which has been optimized for high-performance environments. It has a very low memory footprint compared to other webservers and takes care of cpu-load. Its advanced feature-set (FastCGI, CGI, Auth, Output-Compression, URL-Rewriting and many more) make lighttpd the perfect webserver-software for every server that is suffering load problems. " +category: Net Web +requires: libpcre0 cygwin zlib openssl libbzip2_1 crypt diff --git a/debian/.cvsignore b/debian/.cvsignore new file mode 100644 index 00000000..3dda7298 --- /dev/null +++ b/debian/.cvsignore @@ -0,0 +1,2 @@ +Makefile.in +Makefile diff --git a/debian/Makefile.am b/debian/Makefile.am new file mode 100644 index 00000000..f37933df --- /dev/null +++ b/debian/Makefile.am @@ -0,0 +1,27 @@ +EXTRA_DIST=README.Debian.ex \ +changelog \ +compat \ +conffiles \ +control \ +copyright \ +cron.d.ex \ +dirs \ +docs \ +emacsen-install.ex \ +emacsen-remove.ex \ +emacsen-startup.ex \ +init.d \ +lighttpd-default.ex \ +lighttpd.conf \ +lighttpd.doc-base.EX \ +lighttpd.logrotate \ +lighttpd.postinst \ +manpage.1.ex \ +manpage.sgml.ex \ +manpage.xml.ex \ +menu.ex \ +postrm.ex \ +preinst.ex \ +prerm.ex \ +rules \ +watch.ex diff --git a/debian/README.Debian.ex b/debian/README.Debian.ex new file mode 100644 index 00000000..48cb2c71 --- /dev/null +++ b/debian/README.Debian.ex @@ -0,0 +1,6 @@ +lighttpd for Debian +------------------- + +<possible notes regarding this package - if none, delete this file> + + -- Vincent Wagelaar <vincent@hannibal.lr-s.tudelft.nl>, Wed, 24 Mar 2004 08:20:58 +0100 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..d3dde99d --- /dev/null +++ b/debian/changelog @@ -0,0 +1,144 @@ +lighttpd (1.3.10-1) unstable; urgency=low + + * updated to 1.3.10 + + -- Jan Kneschke <jan@kneschke.de> Sun, 06 Feb 2005 15:41:00 +0000 + +lighttpd (1.3.9-1) unstable; urgency=low + + * updated to 1.3.9 + + -- Jan Kneschke <jan@kneschke.de> Sun, 06 Feb 2005 02:30:00 +0000 + +lighttpd (1.3.8-1) unstable; urgency=low + + * updated to 1.3.8 + + -- Jan Kneschke <jan@kneschke.de> Sun, 30 Jan 2005 00:49:00 +0000 + +lighttpd (1.3.7-1) unstable; urgency=low + + * updated to 1.3.7 + * turned on SSL by default in debian packages + + -- Jan Kneschke <jan@kneschke.de> Tue, 10 Dec 2004 19:09:15 +0000 + +lighttpd (1.3.6-1) unstable; urgency=low + + * updated to 1.3.6 + + -- Jan Kneschke <jan@kneschke.de> Tue, 03 Nov 2004 19:09:15 +0000 + +lighttpd (1.3.5-1) unstable; urgency=low + + * updated to 1.3.5 + * added mod_alias + * added mod_userdir + * added the exec command to the SSI handler + * added a switch to disable follow-symlinks + * added a switch to disable IPv6 at compile-time + * fixed compilation on FreeBSD and NetBSD 1.3.x + * fixed segfault in pipelining + * fixed a segfault in writev() handler if LFS is used + + -- Jan Kneschke <jan@kneschke.de> Sun, 31 Oct 2004 19:09:15 +0000 + +lighttpd (1.3.4-1) unstable; urgency=low + + * updated to 1.3.4 + + -- Jan Kneschke <jan@kneschke.de> Sun, 24 Oct 2004 19:09:15 +0000 + +lighttpd (1.3.1-1) unstable; urgency=low + + * updated to 1.3.1 + + -- Jan Kneschke <jan@kneschke.de> Sat, 30 Sep 2004 19:09:15 +0000 + +lighttpd (1.2.8-1) unstable; urgency=low + + * updated to 1.2.8 + + -- Jan Kneschke <jan@kneschke.de> Sat, 11 Sep 2004 19:09:15 +0000 + +lighttpd (1.2.7-1) unstable; urgency=low + + * added mod_rrdtool for internal statistics + * added xattr support + * added user-controlable timeouts + * improved documentation for many plugins + * fixed POST requests for mod_proxy + * fixed rare hang with CGI + * fixed seg-fault if no configfile is specified + * fixed rare problem in FastCGI header generation + + -- Jan Kneschke <jan@kneschke.de> Sat, 04 Sep 2004 19:09:15 +0000 + +lighttpd (1.2.5-1) unstable; urgency=low + + * added skeleton for solaris 10 port-API + * added compression support even if no cachedir is set + * added conditional configoptions + * fixed compilation on OpenBSD + * fixed kqueue support + * fixed pipelining bug + * fixed parallel build (triggered by Gentoo) + * updated debian postinst + + -- Jan Kneschke <jan@kneschke.de> Sun, 10 Aug 2004 19:09:15 +0000 + +lighttpd (1.2.4-1) unstable; urgency=low + + * added kqueue support + * added server-side includes (mod_ssi) + * fixed large post uploads in fastcgi + * fixed rt-signals handling of delayed events + + -- Jan Kneschke <jan@kneschke.de> Sat, 31 Jul 2004 10:09:15 +0000 + +lighttpd (1.2.3-1) unstable; urgency=low + + * added a proxy module for Java and friends + * added support to pass accesslog through an external programm + * added mimetypes for text/css and text/javascript + * fixed index-files for FastCGI if webserver is in chroot + * fixed error messages of CGI process fails to exec() + * fixed detection of pcre on IRIX and FreeBSD + * fixed timestamps in Last-Modified checks + * fixed 64bit builds + * fixed mmap-caching of large files + * relaxed the HTTP parser on empty headerfields + + -- Jan Kneschke <jan@kneschke.de> Sat, 10 Jul 2004 20:37:01 +0200 + +lighttpd (1.2.2-1) unstable; urgency=low + + * added support for unix domain sockets in FastCGI + * fixed mmap caching + * fixed compile-time check for linux sendfile() + * fixed check for pcre.h on Fedora Core 2 + + -- Jan Kneschke <jan@kneschke.de> Tue, 15 Jun 2004 20:37:01 +0100 + + +lighttpd (1.1.4-1) unstable; urgency=low + + * New upstream release. + + -- Vincent Wagelaar <vincent@ricardis.tudelft.nl> Sun, 04 Apr 2004 21:44:58 +0100 + + +lighttpd (1.1.3-1) unstable; urgency=low + + * New upstream release. + + -- Vincent Wagelaar <vincent@ricardis.tudelft.nl> Wed, 24 Mar 2004 08:20:58 +0100 + + + +lighttpd (1.1.2-1) unstable; urgency=low + + * Initial Release. + + -- Vincent Wagelaar <vincent@ricardis.tudelft.nl> Wed, 24 Mar 2004 08:20:58 +0100 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..b8626c4c --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +4 diff --git a/debian/conffiles b/debian/conffiles new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/debian/conffiles diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..bfeccb70 --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: lighttpd +Section: web +Priority: optional +Maintainer: Jan Kneschke <jan@kneschke.de> +Build-Depends: debhelper (>= 4.0.0), libssl-dev, zlib1g-dev, libbz2-dev, libpcre3-dev +Standards-Version: 3.6.0 + +Package: lighttpd +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Provides: httpd, httpd-cgi +Suggests: openssl, rrdtool +Description: A fast webserver with minimal memory footprint + lighttpd is intended to be a frontend for ad-servers which have to deliver + small files concurrently to many connections. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..7cdb5b38 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,38 @@ +This package was debianized by Vincent Wagelaar <vincent@ricardis.tudelft.nl> on +Wed, 24 Mar 2004 08:20:58 +0100. + +It was downloaded from http://www.incremental.de/products/lighttpd/download/ + +Upstream Author: Jan Kneschke <jan@kneschke.de> + +Copyright: + +Copyright (c) 2004, Jan Kneschke, incremental + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of the 'incremental' nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/debian/cron.d.ex b/debian/cron.d.ex new file mode 100644 index 00000000..59a20abd --- /dev/null +++ b/debian/cron.d.ex @@ -0,0 +1,4 @@ +# +# Regular cron jobs for the lighttpd package +# +0 4 * * * root lighttpd_maintenance diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 00000000..ca882bbb --- /dev/null +++ b/debian/dirs @@ -0,0 +1,2 @@ +usr/bin +usr/sbin diff --git a/debian/docs b/debian/docs new file mode 100644 index 00000000..35f23816 --- /dev/null +++ b/debian/docs @@ -0,0 +1,3 @@ +NEWS +README +doc diff --git a/debian/emacsen-install.ex b/debian/emacsen-install.ex new file mode 100644 index 00000000..3a32ca03 --- /dev/null +++ b/debian/emacsen-install.ex @@ -0,0 +1,45 @@ +#! /bin/sh -e +# /usr/lib/emacsen-common/packages/install/lighttpd + +# Written by Jim Van Zandt <jrv@vanzandt.mv.com>, borrowing heavily +# from the install scripts for gettext by Santiago Vila +# <sanvila@ctv.es> and octave by Dirk Eddelbuettel <edd@debian.org>. + +FLAVOR=$1 +PACKAGE=lighttpd + +if [ ${FLAVOR} = emacs ]; then exit 0; fi + +echo install/${PACKAGE}: Handling install for emacsen flavor ${FLAVOR} + +#FLAVORTEST=`echo $FLAVOR | cut -c-6` +#if [ ${FLAVORTEST} = xemacs ] ; then +# SITEFLAG="-no-site-file" +#else +# SITEFLAG="--no-site-file" +#fi +FLAGS="${SITEFLAG} -q -batch -l path.el -f batch-byte-compile" + +ELDIR=/usr/share/emacs/site-lisp/${PACKAGE} +ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE} + +# Install-info-altdir does not actually exist. +# Maybe somebody will write it. +if test -x /usr/sbin/install-info-altdir; then + echo install/${PACKAGE}: install Info links for ${FLAVOR} + install-info-altdir --quiet --section "" "" --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz +fi + +install -m 755 -d ${ELCDIR} +cd ${ELDIR} +FILES=`echo *.el` +cp ${FILES} ${ELCDIR} +cd ${ELCDIR} + +cat << EOF > path.el +(setq load-path (cons "." load-path) byte-compile-warnings nil) +EOF +${FLAVOR} ${FLAGS} ${FILES} +rm -f *.el path.el + +exit 0 diff --git a/debian/emacsen-remove.ex b/debian/emacsen-remove.ex new file mode 100644 index 00000000..e049ae32 --- /dev/null +++ b/debian/emacsen-remove.ex @@ -0,0 +1,15 @@ +#!/bin/sh -e +# /usr/lib/emacsen-common/packages/remove/lighttpd + +FLAVOR=$1 +PACKAGE=lighttpd + +if [ ${FLAVOR} != emacs ]; then + if test -x /usr/sbin/install-info-altdir; then + echo remove/${PACKAGE}: removing Info links for ${FLAVOR} + install-info-altdir --quiet --remove --dirname=${FLAVOR} /usr/info/lighttpd.info.gz + fi + + echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR} + rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE} +fi diff --git a/debian/emacsen-startup.ex b/debian/emacsen-startup.ex new file mode 100644 index 00000000..75980033 --- /dev/null +++ b/debian/emacsen-startup.ex @@ -0,0 +1,19 @@ +;; -*-emacs-lisp-*- +;; +;; Emacs startup file for the Debian lighttpd package +;; +;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at> +;; Modified by Dirk Eddelbuettel <edd@debian.org> +;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com> + +;; The lighttpd package follows the Debian/GNU Linux 'emacsen' policy and +;; byte-compiles its elisp files for each 'emacs flavor' (emacs19, +;; xemacs19, emacs20, xemacs20...). The compiled code is then +;; installed in a subdirectory of the respective site-lisp directory. +;; We have to add this to the load-path: +(let ((package-dir (concat "/usr/share/" + (symbol-name flavor) + "/site-lisp/lighttpd"))) + (when (file-directory-p package-dir) + (setq load-path (cons package-dir load-path)))) + diff --git a/debian/init.d b/debian/init.d new file mode 100644 index 00000000..9822eeb0 --- /dev/null +++ b/debian/init.d @@ -0,0 +1,78 @@ +#! /bin/sh +# +# skeleton example file to build /etc/init.d/ scripts. +# This file should be used to construct scripts for /etc/init.d. +# +# Written by Miquel van Smoorenburg <miquels@cistron.nl>. +# Modified for Debian +# by Ian Murdock <imurdock@gnu.ai.mit.edu>. +# +# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl +# + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/lighttpd +OPTS="-f /etc/lighttpd/lighttpd.conf" +NAME=lighttpd +DESC=lighttpd + +test -x $DAEMON || exit 0 + +# Include lighttpd defaults if available +if [ -f /etc/default/lighttpd ] ; then + . /etc/default/lighttpd +fi + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet \ + --pidfile /var/run/$NAME.pid --exec $DAEMON -- $OPTS + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + if start-stop-daemon --stop --pidfile /var/run/$NAME.pid \ + --exec $DAEMON; then + rm -f /var/run/$NAME.pid + echo "$NAME." + fi + ;; + reload) + # + # If the daemon can reload its config files on the fly + # for example by sending it SIGHUP, do it here. + # + # If the daemon responds to changes in its config file + # directly anyway, make this a do-nothing entry. + # + echo "Reloading $DESC configuration files." + start-stop-daemon --stop --signal 1 --quiet --pidfile /var/run/$NAME.pid \ + --exec $DAEMON + ;; + restart|force-reload) + # + # If the "reload" option is implemented, move the "force-reload" + # option to the "reload" entry above. If not, "force-reload" is + # just the same as "restart". + # + echo -n "Restarting $DESC: " + start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/$NAME.pid \ + --exec $DAEMON + rm -f /var/run/$NAME.pid + sleep 1 + start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ + --exec $DAEMON -- $OPTS + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + #echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/lighttpd-default.ex b/debian/lighttpd-default.ex new file mode 100644 index 00000000..4866702b --- /dev/null +++ b/debian/lighttpd-default.ex @@ -0,0 +1,10 @@ +# Defaults for lighttpd initscript +# sourced by /etc/init.d/lighttpd +# installed at /etc/default/lighttpd by the maintainer scripts + +# +# This is a POSIX shell fragment +# + +# Additional options that are passed to the Daemon. +DAEMON_OPTS="" diff --git a/debian/lighttpd.conf b/debian/lighttpd.conf new file mode 100644 index 00000000..389181ec --- /dev/null +++ b/debian/lighttpd.conf @@ -0,0 +1,232 @@ +# lighttpd configuration file +# +# use a it as base for lighttpd 1.0.0 and above +# +# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $ + +############ Options you really have to take care of #################### + +## modules to load +# at least mod_access and mod_accesslog should be loaded +# all other module should only be loaded if really neccesary +# - saves some time +# - saves memory +server.modules = ( +# "mod_rewrite", +# "mod_redirect", + "mod_access", +# "mod_auth", +# "mod_status", +# "mod_fastcgi", +# "mod_simple_vhost", +# "mod_evhost", +# "mod_cgi", +# "mod_compress", +# "mod_ssi", +# "mod_usertrack", +# "mod_rrdtool", + "mod_accesslog" ) + +## a static document-root, for virtual-hosting take look at the +## server.virtual-* options +server.document-root = "/var/www/" + +## where to send error-messages to +server.errorlog = "/var/log/lighttpd/error.log" + +# files to check for if .../ is requested +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +# mimetype mapping +mimetype.assign = ( + ".pdf" => "application/pdf", + ".sig" => "application/pgp-signature", + ".spl" => "application/futuresplash", + ".class" => "application/octet-stream", + ".ps" => "application/postscript", + ".torrent" => "application/x-bittorrent", + ".dvi" => "application/x-dvi", + ".gz" => "application/x-gzip", + ".pac" => "application/x-ns-proxy-autoconfig", + ".swf" => "application/x-shockwave-flash", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".tar" => "application/x-tar", + ".zip" => "application/zip", + ".mp3" => "audio/mpeg", + ".m3u" => "audio/x-mpegurl", + ".wma" => "audio/x-ms-wma", + ".wax" => "audio/x-ms-wax", + ".ogg" => "audio/x-wav", + ".wav" => "audio/x-wav", + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".xbm" => "image/x-xbitmap", + ".xpm" => "image/x-xpixmap", + ".xwd" => "image/x-xwindowdump", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".js" => "text/javascript", + ".asc" => "text/plain", + ".c" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mov" => "video/quicktime", + ".qt" => "video/quicktime", + ".avi" => "video/x-msvideo", + ".asf" => "video/x-ms-asf", + ".asx" => "video/x-ms-asf", + ".wmv" => "video/x-ms-wmv" + ) + +# Use the "Content-Type" extended attribute to obtain mime type if possible +# mimetypes.use-xattr = "enable" + +#### accesslog module +accesslog.filename = "/var/log/lighttpd/access.log" + +## deny access the file-extensions +# +# ~ is for backupfiles from vi, emacs, joe, ... +# .inc is often used for code includes which should in general not be part +# of the document-root +url.access-deny = ( "~", ".inc" ) + + + +######### Options that are good to be but not neccesary to be changed ####### + +## bind to port (default: 80) +#server.port = 81 + +## bind to localhost (default: all interfaces) +#server.bind = "grisu.home.kneschke.de" + +## error-handler for status 404 +#server.error-handler-404 = "/error-handler.html" +#server.error-handler-404 = "/error-handler.php" + +## to help the rc.scripts +server.pid-file = "/var/run/lighttpd.pid" + + +###### virtual hosts +## +## If you want name-based virtual hosting add the next three settings and load +## mod_simple_vhost +## +## document-root = +## virtual-server-root + virtual-server-default-host + virtual-server-docroot or +## virtual-server-root + http-host + virtual-server-docroot +## +#simple-vhost.server-root = "/home/weigon/wwwroot/servers/" +#simple-vhost.default-host = "grisu.home.kneschke.de" +#simple-vhost.document-root = "/pages/" + + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +## virtual directory listings +#server.dir-listing = "enable" + +## send unhandled HTTP-header headers to error-log +#debug.dump-unknown-headers = "enable" + +### only root can use these options +# +# chroot() to directory (default: no chroot() ) +#server.chroot = "/" + +## change uid to <uid> (default: don't care) +server.username = "www-data" + +## change uid to <uid> (default: don't care) +server.groupname = "www-data" + +#### compress module +#compress.cache-dir = "/var/tmp/lighttpd/cache/compress/" +#compress.filetype = ("text/plain", "text/html") + +#### fastcgi module +## read fastcgi.txt for more info +#fastcgi.server = ( ".php" => +# ( "localhost" => +# ( +# "socket" => "/tmp/php-fastcgi.socket", +# "bin-path" => "/usr/local/bin/php" +# ) +# ) +# ) + +#### CGI module +#cgi.assign = ( ".pl" => "/usr/bin/perl", +# ".cgi" => "/usr/bin/perl" ) + +#### SSL engine +#ssl.engine = "enable" +#ssl.pemfile = "server.pem" + +#### status module +# status.status-url = "/server-status" +# status.config-url = "/server-config" + +#### auth module +## read authentification.txt for more info +# auth.backend = "plain" +# auth.backend.plain.userfile = "lighttpd.user" +# auth.backend.plain.groupfile = "lighttpd.group" + +# auth.backend.ldap.hostname = "localhost" +# auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +# auth.backend.ldap.filter = "(uid=$)" + +# auth.require = ( "/server-status" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "group=www|user=jan|host=192.168.2.10" +# ), +# "/server-info" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "group=www|user=jan|host=192.168.2.10" +# ) +# ) + +#### url handling modules (rewrite, redirect, access) +# url.rewrite = ( "^/$" => "/server-status" ) +# url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" ) + +# +# 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/storage/dev/www/%3/htdocs/" + +#### expire module +# expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#### ssi +# ssi.extension = ( ".shtml" ) + +#### rrdtool +# rrdtool.binary = "/usr/bin/rrdtool" +# rrdtool.db-name = "/var/www/lighttpd.rrd" diff --git a/debian/lighttpd.doc-base.EX b/debian/lighttpd.doc-base.EX new file mode 100644 index 00000000..edfecd13 --- /dev/null +++ b/debian/lighttpd.doc-base.EX @@ -0,0 +1,22 @@ +Document: lighttpd +Title: Debian lighttpd Manual +Author: <insert document author here> +Abstract: This manual describes what lighttpd is + and how it can be used to + manage online manuals on Debian systems. +Section: unknown + +Format: debiandoc-sgml +Files: /usr/share/doc/lighttpd/lighttpd.sgml.gz + +Format: postscript +Files: /usr/share/doc/lighttpd/lighttpd.ps.gz + +Format: text +Files: /usr/share/doc/lighttpd/lighttpd.text.gz + +Format: HTML +Index: /usr/share/doc/lighttpd/html/index.html +Files: /usr/share/doc/lighttpd/html/*.html + + diff --git a/debian/lighttpd.logrotate b/debian/lighttpd.logrotate new file mode 100644 index 00000000..e2fb5571 --- /dev/null +++ b/debian/lighttpd.logrotate @@ -0,0 +1,18 @@ +/var/log/lighttpd/*.log { + daily + missingok + copytruncate + rotate 7 + compress + notifempty + sharedscripts + postrotate + if [ -f /var/run/lighttpd.pid ]; then \ + if [ -x /usr/sbin/invoke-rc.d ]; then \ + invoke-rc.d lighttpd reload > /dev/null; \ + else \ + /etc/init.d/lighttpd reload > /dev/null; \ + fi; \ + fi; + endscript +} diff --git a/debian/lighttpd.postinst b/debian/lighttpd.postinst new file mode 100644 index 00000000..f3ee3ed9 --- /dev/null +++ b/debian/lighttpd.postinst @@ -0,0 +1,42 @@ +#! /bin/sh +# postinst script for lighttpd +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postinst> `configure' <most-recently-configured-version> +# * <old-postinst> `abort-upgrade' <new version> +# * <conflictor's-postinst> `abort-remove' `in-favour' <package> +# <new-version> +# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' +# <failed-install-package> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package +# + +case "$1" in + configure) + chown www-data:www-data /var/log/lighttpd + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/manpage.1.ex b/debian/manpage.1.ex new file mode 100644 index 00000000..1396b394 --- /dev/null +++ b/debian/manpage.1.ex @@ -0,0 +1,60 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH LIGHTTPD SECTION "March 24, 2004" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp <n> insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +lighttpd \- program to do something +.SH SYNOPSIS +.B lighttpd +.RI [ options ] " files" ... +.br +.B bar +.RI [ options ] " files" ... +.SH DESCRIPTION +This manual page documents briefly the +.B lighttpd +and +.B bar +commands. +This manual page was written for the Debian distribution +because the original program does not have a manual page. +Instead, it has documentation in the GNU Info format; see below. +.PP +.\" TeX users may be more comfortable with the \fB<whatever>\fP and +.\" \fI<whatever>\fP escape sequences to invode bold face and italics, +.\" respectively. +\fBlighttpd\fP is a program that... +.SH OPTIONS +These programs follow the usual GNU command line syntax, with long +options starting with two dashes (`-'). +A summary of options is included below. +For a complete description, see the Info files. +.TP +.B \-h, \-\-help +Show summary of options. +.TP +.B \-v, \-\-version +Show version of program. +.SH SEE ALSO +.BR bar (1), +.BR baz (1). +.br +The programs are documented fully by +.IR "The Rise and Fall of a Fooish Bar" , +available via the Info system. +.SH AUTHOR +This manual page was written by Vincent Wagelaar <vincent@hannibal.lr-s.tudelft.nl>, +for the Debian project (but may be used by others). diff --git a/debian/manpage.sgml.ex b/debian/manpage.sgml.ex new file mode 100644 index 00000000..4efa3da4 --- /dev/null +++ b/debian/manpage.sgml.ex @@ -0,0 +1,156 @@ +<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [ + +<!-- Process this file with docbook-to-man to generate an nroff manual + page: `docbook-to-man manpage.sgml > manpage.1'. You may view + the manual page with: `docbook-to-man manpage.sgml | nroff -man | + less'. A typical entry in a Makefile or Makefile.am is: + +manpage.1: manpage.sgml + docbook-to-man $< > $@ + + + The docbook-to-man binary is found in the docbook-to-man package. + Please remember that if you create the nroff version in one of the + debian/rules file targets (such as build), you will need to include + docbook-to-man in your Build-Depends control field. + + --> + + <!-- Fill in your name for FIRSTNAME and SURNAME. --> + <!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>"> + <!ENTITY dhsurname "<surname>SURNAME</surname>"> + <!-- Please adjust the date whenever revising the manpage. --> + <!ENTITY dhdate "<date>March 24, 2004</date>"> + <!-- SECTION should be 1-8, maybe w/ subsection other parameters are + allowed: see man(7), man(1). --> + <!ENTITY dhsection "<manvolnum>SECTION</manvolnum>"> + <!ENTITY dhemail "<email>vincent@hannibal.lr-s.tudelft.nl</email>"> + <!ENTITY dhusername "Vincent Wagelaar"> + <!ENTITY dhucpackage "<refentrytitle>LIGHTTPD</refentrytitle>"> + <!ENTITY dhpackage "lighttpd"> + + <!ENTITY debian "<productname>Debian</productname>"> + <!ENTITY gnu "<acronym>GNU</acronym>"> + <!ENTITY gpl "&gnu; <acronym>GPL</acronym>"> +]> + +<refentry> + <refentryinfo> + <address> + &dhemail; + </address> + <author> + &dhfirstname; + &dhsurname; + </author> + <copyright> + <year>2003</year> + <holder>&dhusername;</holder> + </copyright> + &dhdate; + </refentryinfo> + <refmeta> + &dhucpackage; + + &dhsection; + </refmeta> + <refnamediv> + <refname>&dhpackage;</refname> + + <refpurpose>program to do something</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>&dhpackage;</command> + + <arg><option>-e <replaceable>this</replaceable></option></arg> + + <arg><option>--example <replaceable>that</replaceable></option></arg> + </cmdsynopsis> + </refsynopsisdiv> + <refsect1> + <title>DESCRIPTION</title> + + <para>This manual page documents briefly the + <command>&dhpackage;</command> and <command>bar</command> + commands.</para> + + <para>This manual page was written for the &debian; distribution + because the original program does not have a manual page. + Instead, it has documentation in the &gnu; + <application>Info</application> format; see below.</para> + + <para><command>&dhpackage;</command> is a program that...</para> + + </refsect1> + <refsect1> + <title>OPTIONS</title> + + <para>These programs follow the usual &gnu; command line syntax, + with long options starting with two dashes (`-'). A summary of + options is included below. For a complete description, see the + <application>Info</application> files.</para> + + <variablelist> + <varlistentry> + <term><option>-h</option> + <option>--help</option> + </term> + <listitem> + <para>Show summary of options.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>-v</option> + <option>--version</option> + </term> + <listitem> + <para>Show version of program.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>SEE ALSO</title> + + <para>bar (1), baz (1).</para> + + <para>The programs are documented fully by <citetitle>The Rise and + Fall of a Fooish Bar</citetitle> available via the + <application>Info</application> system.</para> + </refsect1> + <refsect1> + <title>AUTHOR</title> + + <para>This manual page was written by &dhusername; &dhemail; for + the &debian; system (but may be used by others). Permission is + granted to copy, distribute and/or modify this document under + the terms of the &gnu; General Public License, Version 2 any + later version published by the Free Software Foundation. + </para> + <para> + On Debian systems, the complete text of the GNU General Public + License can be found in /usr/share/common-licenses/GPL. + </para> + + </refsect1> +</refentry> + +<!-- Keep this comment at the end of the file +Local variables: +mode: sgml +sgml-omittag:t +sgml-shorttag:t +sgml-minimize-attributes:nil +sgml-always-quote-attributes:t +sgml-indent-step:2 +sgml-indent-data:t +sgml-parent-document:nil +sgml-default-dtd-file:nil +sgml-exposed-tags:nil +sgml-local-catalogs:nil +sgml-local-ecat-files:nil +End: +--> + + diff --git a/debian/manpage.xml.ex b/debian/manpage.xml.ex new file mode 100644 index 00000000..c2c58ee9 --- /dev/null +++ b/debian/manpage.xml.ex @@ -0,0 +1,148 @@ +<?xml version='1.0' encoding='ISO-8859-1'?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ + +<!-- + +Process this file with an XSLT processor: `xsltproc \ +-''-nonet /usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\ +manpages/docbook.xsl manpage.dbk'. A manual page +<package>.<section> will be generated. You may view the +manual page with: nroff -man <package>.<section> | less'. A +typical entry in a Makefile or Makefile.am is: + +DB2MAN=/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\ +manpages/docbook.xsl +XP=xsltproc -''-nonet + +manpage.1: manpage.dbk + $(XP) $(DB2MAN) $< + +The xsltproc binary is found in the xsltproc package. The +XSL files are in docbook-xsl. Please remember that if you +create the nroff version in one of the debian/rules file +targets (such as build), you will need to include xsltproc +and docbook-xsl in your Build-Depends control field. + +--> + + <!-- Fill in your name for FIRSTNAME and SURNAME. --> + <!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>"> + <!ENTITY dhsurname "<surname>SURNAME</surname>"> + <!-- Please adjust the date whenever revising the manpage. --> + <!ENTITY dhdate "<date>March 24, 2004</date>"> + <!-- SECTION should be 1-8, maybe w/ subsection other parameters are + allowed: see man(7), man(1). --> + <!ENTITY dhsection "<manvolnum>SECTION</manvolnum>"> + <!ENTITY dhemail "<email>vincent@hannibal.lr-s.tudelft.nl</email>"> + <!ENTITY dhusername "Vincent Wagelaar"> + <!ENTITY dhucpackage "<refentrytitle>LIGHTTPD</refentrytitle>"> + <!ENTITY dhpackage "lighttpd"> + + <!ENTITY debian "<productname>Debian</productname>"> + <!ENTITY gnu "<acronym>GNU</acronym>"> + <!ENTITY gpl "&gnu; <acronym>GPL</acronym>"> +]> + +<refentry> + <refentryinfo> + <address> + &dhemail; + </address> + <author> + &dhfirstname; + &dhsurname; + </author> + <copyright> + <year>2003</year> + <holder>&dhusername;</holder> + </copyright> + &dhdate; + </refentryinfo> + <refmeta> + &dhucpackage; + + &dhsection; + </refmeta> + <refnamediv> + <refname>&dhpackage;</refname> + + <refpurpose>program to do something</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>&dhpackage;</command> + + <arg><option>-e <replaceable>this</replaceable></option></arg> + + <arg><option>--example <replaceable>that</replaceable></option></arg> + </cmdsynopsis> + </refsynopsisdiv> + <refsect1> + <title>DESCRIPTION</title> + + <para>This manual page documents briefly the + <command>&dhpackage;</command> and <command>bar</command> + commands.</para> + + <para>This manual page was written for the &debian; distribution + because the original program does not have a manual page. + Instead, it has documentation in the &gnu; + <application>Info</application> format; see below.</para> + + <para><command>&dhpackage;</command> is a program that...</para> + + </refsect1> + <refsect1> + <title>OPTIONS</title> + + <para>These programs follow the usual &gnu; command line syntax, + with long options starting with two dashes (`-'). A summary of + options is included below. For a complete description, see the + <application>Info</application> files.</para> + + <variablelist> + <varlistentry> + <term><option>-h</option> + <option>--help</option> + </term> + <listitem> + <para>Show summary of options.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><option>-v</option> + <option>--version</option> + </term> + <listitem> + <para>Show version of program.</para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>SEE ALSO</title> + + <para>bar (1), baz (1).</para> + + <para>The programs are documented fully by <citetitle>The Rise and + Fall of a Fooish Bar</citetitle> available via the + <application>Info</application> system.</para> + </refsect1> + <refsect1> + <title>AUTHOR</title> + + <para>This manual page was written by &dhusername; &dhemail; for + the &debian; system (but may be used by others). Permission is + granted to copy, distribute and/or modify this document under + the terms of the &gnu; General Public License, Version 2 any + later version published by the Free Software Foundation. + </para> + <para> + On Debian systems, the complete text of the GNU General Public + License can be found in /usr/share/common-licenses/GPL. + </para> + + </refsect1> +</refentry> + diff --git a/debian/menu.ex b/debian/menu.ex new file mode 100644 index 00000000..07134b69 --- /dev/null +++ b/debian/menu.ex @@ -0,0 +1,2 @@ +?package(lighttpd):needs=X11|text|vc|wm section=Apps/see-menu-manual\ + title="lighttpd" command="/usr/bin/lighttpd" diff --git a/debian/postrm.ex b/debian/postrm.ex new file mode 100644 index 00000000..3645f893 --- /dev/null +++ b/debian/postrm.ex @@ -0,0 +1,38 @@ +#! /bin/sh +# postrm script for lighttpd +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <r>overwrit>r> <new-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + + + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/preinst.ex b/debian/preinst.ex new file mode 100644 index 00000000..6e888f3f --- /dev/null +++ b/debian/preinst.ex @@ -0,0 +1,44 @@ +#! /bin/sh +# preinst script for lighttpd +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <new-preinst> `install' +# * <new-preinst> `install' <old-version> +# * <new-preinst> `upgrade' <old-version> +# * <old-preinst> `abort-upgrade' <new-version> +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) +# if [ "$1" = "upgrade" ] +# then +# start-stop-daemon --stop --quiet --oknodo \ +# --pidfile /var/run/lighttpd.pid \ +# --exec /usr/sbin/lighttpd 2>/dev/null || true +# fi + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/prerm.ex b/debian/prerm.ex new file mode 100644 index 00000000..538ea1c8 --- /dev/null +++ b/debian/prerm.ex @@ -0,0 +1,39 @@ +#! /bin/sh +# prerm script for lighttpd +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <prerm> `remove' +# * <old-prerm> `upgrade' <new-version> +# * <new-prerm> `failed-upgrade' <old-version> +# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> +# * <deconfigured's-prerm> `deconfigure' `in-favour' +# <package-being-installed> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|deconfigure) +# install-info --quiet --remove /usr/info/lighttpd.info.gz + ;; + failed-upgrade) + ;; + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..5e3d0476 --- /dev/null +++ b/debian/rules @@ -0,0 +1,117 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 to 1999 by Joey Hess. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) + + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif +ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) + INSTALL_PROGRAM += -s +endif + +config.status: configure + dh_testdir + # Add here commands to configure the package. + CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) \ + --build=$(DEB_BUILD_GNU_TYPE)\ + --prefix=/usr \ + --mandir=\$${prefix}/share/man \ + --infodir=\$${prefix}/share/info \ + --sysconfdir=/etc/lighttpd \ + --libdir=/usr/lib/lighttpd \ + --with-openssl + + + +build: build-stamp + +build-stamp: config.status + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + #/usr/bin/docbook-to-man debian/lighttpd.sgml > lighttpd.1 + + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) distclean +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/lighttpd. + $(MAKE) install DESTDIR=$(CURDIR)/debian/lighttpd + install -d debian/lighttpd/etc/{lighttpd,init.d,logrotate.d} + install -d debian/lighttpd/var/{www,log/lighttpd} + install -m 644 debian/*conf debian/lighttpd/etc/lighttpd/ + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf + dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime + dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_python +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/debian/watch.ex b/debian/watch.ex new file mode 100644 index 00000000..87e1b9fd --- /dev/null +++ b/debian/watch.ex @@ -0,0 +1,6 @@ +# Example watch control file for uscan +# Rename this file to "watch" and then you can run the "uscan" command +# to check for upstream updates and more. +# Site Directory Pattern Version Script +version=2 +sunsite.unc.edu /pub/Linux/Incoming lighttpd-(.*)\.tar\.gz debian uupdate diff --git a/distribute.sh.in b/distribute.sh.in new file mode 100644 index 00000000..8db25094 --- /dev/null +++ b/distribute.sh.in @@ -0,0 +1,92 @@ +#!/bin/sh +PACKAGE=@PACKAGE_TARNAME@ +VERSION=@VERSION@ +NAME=@PACKAGE_TARNAME@-@VERSION@ + +DISTDIR="/home/weigon/wwwroot/servers/www.lighttpd.net/pages/download/" +FILES="content-3 content-4 content-5 \ + /usr/src/packages/RPMS/i586/${NAME}-1.i586.rpm \ + /usr/src/packages/SRPMS/${NAME}-1.src.rpm \ + ${NAME}.tar.gz \ + NEWS.html \ + ChangeLog \ + release-news.${VERSION}.txt \ + ${NAME}.tar.gz.sig" +DLURL="http://www.lighttpd.net/download" +pack=0 +echo $1 +case "$1" in + --pack) pack=1;; +esac + +echo ${nopack} + +if test x${pack} = x1; then + make distcheck && rpmbuild -ta --nodeps ${NAME}.tar.gz + gpg --detach-sign ${NAME}.tar.gz + rpm --addsign /usr/src/packages/RPMS/i586/${NAME}-1.i586.rpm /usr/src/packages/SRPMS/${NAME}-1.src.rpm +fi + +MD5RPM=`md5sum /usr/src/packages/RPMS/i586/${NAME}-1.i586.rpm| cut -b 1-32` +MD5SRPM=`md5sum /usr/src/packages/SRPMS/${NAME}-1.src.rpm| cut -b 1-32` +MD5TGZ=`md5sum ${NAME}.tar.gz| cut -b 1-32` +DATE=`date +'%Y-%m-%d %H:%M'` +NEWS=`cat NEWS | sed "/^- ${VERSION}/,/^-/p;d" | sed "/^- /d;/^$/d"` +DLNAME="${DLURL}/${NAME}" + +cat > release-news.${VERSION} <<EOF +!${PACKAGE} ${VERSION} - ${DATE} +- *Changes* +++QUOTED++ +<pre>${NEWS} +</pre><br /> +--QUOTED-- +- *Download* +- (${NAME}-1.i586.rpm|${NAME}-1.i586.rpm) [built on SuSE 9.0] + MD5: ${MD5RPM} +- (${NAME}-1.src.rpm|${NAME}-1.src.rpm) + MD5: ${MD5SRPM} +- (${NAME}.tar.gz|${NAME}.tar.gz) + MD5: ${MD5TGZ} (${NAME}.tar.gz.sig|signature) + +EOF + +cat > release-news.${VERSION}.txt <<EOF +${PACKAGE} ${VERSION} - ${DATE} + +Changes +------- +${NEWS} + +Download +- ${NAME}-1.i586.rpm [built on SuSE 9.0] + ${DLNAME}-1.i586.rpm + MD5: ${MD5RPM} +- ${NAME}-1.src.rpm + ${DLNAME}-1.src.rpm + MD5: ${MD5SRPM} +- ${NAME}.tar.gz + ${DLNAME}.tar.gz + MD5: ${MD5TGZ} + Signature: ${DLNAME}.tar.gz.sig + +EOF + +rst2html.py NEWS > NEWS.html + +cat content-4.foot | sed "/^\!${PACKAGE} ${VERSION}/,/^$/d" > content-4.foot.new +cat release-news.${VERSION} content-4.foot.new > content-4.foot +rm content-4.foot.new +cat content-4.head content-4.foot > content-4 + +for i in ${DISTDIR}; do + cp -u ${FILES} $i +done + +curdir=`pwd` +cd ~/wwwroot/servers/www.lighttpd.net/ +make put +cd ${curdir} + +kmail -s "ANNOUNCE: ${NAME}" --msg `pwd`/release-news.${VERSION}.txt lighttpd-announce@lists.kneschke.de + diff --git a/doc/.cvsignore b/doc/.cvsignore new file mode 100644 index 00000000..3dda7298 --- /dev/null +++ b/doc/.cvsignore @@ -0,0 +1,2 @@ +Makefile.in +Makefile diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..d515024a --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,84 @@ +dist_man1_MANS=lighttpd.1 + + +DOCS=accesslog.txt \ +authentification.txt \ +cgi.txt \ +compress.txt \ +configuration.txt \ +fastcgi-state.txt \ +fastcgi.txt \ +features.txt \ +performance.txt \ +plugins.txt \ +proxy.txt \ +redirect.txt \ +rewrite.txt \ +secdownload.txt \ +security.txt \ +simple-vhost.txt \ +skeleton.txt \ +ssi.txt \ +ssl.txt \ +state.txt \ +rrdtool.txt \ +alias.txt \ +userdir.txt \ +mysqlvhost.txt \ +access.txt \ +traffic-shaping.txt \ +setenv.txt \ +status.txt + +HTMLDOCS=accesslog.html \ + authentification.html \ + cgi.html \ + compress.html \ + configuration.html \ + fastcgi-state.html \ + fastcgi.html \ + features.html \ + performance.html \ + plugins.html \ + proxy.html \ + redirect.html \ + rewrite.html \ + secdownload.html \ + security.html \ + simple-vhost.html \ + skeleton.html \ + ssi.html \ + ssl.html \ + state.html \ + rrdtool.html \ + alias.html \ + userdir.html \ + mysqlvhost.html \ + access.html \ + traffic-shaping.html \ + setenv.html \ + status.html + +EXTRA_DIST=lighttpd.conf lighttpd.user \ + rc.lighttpd rc.lighttpd.redhat sysconfig.lighttpd \ + state.ps.gz rrdtool-graph.sh \ + state.dot fastcgi-state.dot fastcgi-state.ps.gz \ + spawn-php.sh \ + $(DOCS) + +%.html: %.txt + rst2html.py $^ > $@ + + +html: $(HTMLDOCS) + +%.ps.gz: %.ps + gzip $^ + +%.ps: %.dot + dot -Tps -o $@ $^ + +clean-local: + rm -f *.html + + diff --git a/doc/access.txt b/doc/access.txt new file mode 100644 index 00000000..acfdcb72 --- /dev/null +++ b/doc/access.txt @@ -0,0 +1,41 @@ +====== +Access +====== + +------------------ +Module: mod_access +------------------ + +:Author: Allan Wind +:Date: $Date: 2005/01/30 11:34:32 $ +:Revision: $Revision: 1.1 $ + +:abstract: + The access module is used to deny access to files with given trailing path names. + +.. meta:: + :keywords: lighttpd, trailing path access control + +.. contents:: Table of Contents + +Description +=========== + +The access module is used to deny access to files with given trailing path names. + +Options +======= + +url.access-deny + Denies access to all files with any of given trailing path names. + + Default: empty + + Example: :: + + url.access-deny = ( "~", ".inc") + + will deny access to all files ended with a diacritical mark (~) or .inc + such as example~ or example.inc. A trailing diacritical mark is often + used by editors for backup files. And the .inc extension is often used + for include files with code. diff --git a/doc/accesslog.txt b/doc/accesslog.txt new file mode 100644 index 00000000..453b3ea8 --- /dev/null +++ b/doc/accesslog.txt @@ -0,0 +1,126 @@ +========= +Accesslog +========= + +--------------------- +Module: mod_accesslog +--------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + The accesslog module ... + +.. meta:: + :keywords: lighttpd, accesslog, CLF + +.. contents:: Table of Contents + +Description +=========== + +CLF like by default, flexible like apache + +Options +======= + +accesslog.use-syslog + send the accesslog to syslog + + Default: disabled + +accesslog.filename + name of the file where the accesslog should be written too if syslog + is not used. + + if the name starts with a '|' the rest of the name is taken + as the name of a process which will be spawn and will get the + output + + e.g.: :: + + accesslog.filename = "/var/log/lighttpd.log" + + $HTTP["host"] == "mail.example.org" { + accesslog.filename = "|/usr/bin/cronolog" + } + + Default: disabled + +accesslog.format + the format of the logfile + + ====== ================================ + Option Description + ====== ================================ + %% a percent sign + %h name or address of remote-host + %l ident name (not supported) + %u authenticated user + %t timestamp for the request-start + %r request-line + %s status code + %b bytes sent for the body + %i HTTP-header field + %a remote address + %A local address + %B same as %b + %C cookie field (not supported) + %D time used in ms (not supported) + %e environment (not supported) + %f phyiscal filename + %H request protocol (HTTP/1.0, ...) + %m request method (GET, POST, ...) + %n (not supported) + %o `response header`_ + %p server port + %P (not supported) + %q query string + %T time used in seconds + %U request URL + %v server-name + %V (not supported) + %X connection status + %I bytes incomming + %O bytes outgoing + ====== ================================ + + If %s is written %>s or %<s the < and the > are ignored. They are support + for compat with apache. + + %i and %o expect the name of the field which should be written in curly brackets. + + e.g.: :: + + accesslog.format = "%h %l %u %t \"%r\" %b %>s \"%{User-Agent}i\" \"%{Referer}i\"" + + Default: CLF compatible output + +response header +--------------- + +The accesslog module provides a special way to log content from the +application in a accesslog file. It can be used to log the session id into a +logfile. + +If you want to log it into the accesslog just specify the field-name within +a %{...}o like :: + + accesslog.format = "%h %l %u %t \"%r\" %b %>s \"%{User-Agent}i\" \"%{Referer}i\" \"%{X-LIGHTTPD-SID}o\"" + +The prefix ``X-LIGHTTPD-`` is special as every response header starting with +this prefix is assumed to be special for lighttpd and won't be sent out +to the client. + +An example the use this functionality is provided below: :: + + <?php + + session_start(); + + header("X-LIGHTTPD-SID: ".session_id()); + + ?> + diff --git a/doc/alias.txt b/doc/alias.txt new file mode 100644 index 00000000..15012e31 --- /dev/null +++ b/doc/alias.txt @@ -0,0 +1,36 @@ +===== +Alias +===== + +----------------- +Module: mod_alias +----------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + The alias module ... + +.. meta:: + :keywords: lighttpd, alias + +.. contents:: Table of Contents + +Description +=========== + +The alias module is used to specify a special document-root for a given url-subset. + +Options +======= + +alias.url + rewrites the document-root for a URL-subset + + Default: empty + + Example: :: + + alias.url = ( "/cgi-bin/" => "/var/www/servers/www.example.org/cgi-bin/" ) diff --git a/doc/authentification.txt b/doc/authentification.txt new file mode 100644 index 00000000..2528589f --- /dev/null +++ b/doc/authentification.txt @@ -0,0 +1,191 @@ +====================== +Using Authentification +====================== + +---------------- +Module: mod_auth +---------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.3 $ + +:abstract: + The auth module provides ... + +.. meta:: + :keywords: lighttpd, authentification + +.. contents:: Table of Contents + +Description +=========== + +Supported Methods +----------------- + +lighttpd supportes both authentification method described by +RFC 2617: + +basic +````` + +The Basic method transfers the username and the password in +cleartext over the network (base64 encoded) and might result +in security problems if not used in conjunction with a crypted +channel between client and server. + +digest +`````` + +The Digest method only transfers a hashed value over the +network which is performes a lot of work to harden the +authentification process in insecure networks. + +Backends +-------- + +Depending on the method lighttpd provides various way to store +the credentials used for the authentification. + +for basic auth: + +- plain_ +- htpasswd_ (crypt only) +- htdigest_ +- ldap_ + +for digest auth: + +- plain_ +- htdigest_ + + +plain +````` + +A file which contains username and the cleartext password +seperated by a colon. Each entry is terminated by a single +newline.:: + + e.g.: + agent007:secret + + +htpasswd +```````` + +A file which contains username and the crypt()'ed password +seperated by a colon. Each entry is terminated by a single +newline. :: + + e.g.: + agent007:XWY5JwrAVBXsQ + +You can use htpasswd from the apache distribution to manage +those files. :: + + $ htpasswd lighttpd.user.digest agent007 + + +htdigest +```````` + +A file which contains username, realm and the md5()'ed +password seperated by a colon. Each entry is terminated +by a single newline. :: + + e.g.: + agent007:download area:8364d0044ef57b3defcfa141e8f77b65 + +You can use htdigest from the apache distribution to manage +those files. :: + + $ htdigest src/lighttpd.user.digest 'download area' agent007 + +Using md5sum can also generate the password-hash: :: + + $ echo -n "agent007:download area:secret" | md5sum - + 8364d0044ef57b3defcfa141e8f77b65 - + + +ldap +```` + +the ldap backend is basicly performing the following steps +to authenticate a user + +1. connect anonymously (at plugin init) +2. get DN for filter = username +3. auth against ldap server +4. disconnect + +if step 4 is performs without any error the user is +authenticated + +Configuration +============= + +:: + + ## debugging + # 0 for off, 1 for 'auth-ok' messages, 2 for verbose debugging + auth.debug = 0 + + ## type of backend + # plain, htpasswd, ldap or htdigest + auth.backend = "htpasswd" + + # filename of the password storage for + # plain + auth.backend.plain.userfile = "lighttpd-plain.user" + + ## for htpasswd + auth.backend.htpasswd.userfile = "lighttpd-htpasswd.user" + + ## for htdigest + auth.backend.htdigest.userfile = "lighttpd-htdigest.user" + + ## for ldap + # the $ in auth.backend.ldap.filter is replaced by the + # 'username' from the login dialog + auth.backend.ldap.hostname = "localhost" + auth.backend.ldap.base-dn = "dc=my-domain,dc=com" + auth.backend.ldap.filter = "(uid=$)" + + ## restrictions + # set restrictions: + # + # ( <left-part-of-the-url> => + # ( "method" => "digest"/"basic", + # "realm" => <realm>, + # "require" => "user=<username>" ) + # ) + # + # <realm> is a string that is should be display in the dialog + # presented to the user and is also used for the + # digest-algorithm and has to match the realm in the + # htdigest file (if used) + # + + auth.require = ( "/download/" => + ( + "method" => "digest", + "realm" => "download archiv", + "require" => "user=agent007|user=agent008" + ), + "/server-info" => + ( + "method" => "digest", + "realm" => "download archiv", + "require" => "user=jan" + ) + ) + +Limitiations +============ + +- The implementation of digest method is currently not + completely conforming to the standard as it is still allowing + a replay attack. + diff --git a/doc/cgi.txt b/doc/cgi.txt new file mode 100644 index 00000000..6642feaf --- /dev/null +++ b/doc/cgi.txt @@ -0,0 +1,50 @@ +=== +CGI +=== + +--------------- +Module: mod_cgi +--------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + cgi module provide a CGI conforming interface + +.. meta:: + :keywords: lighttpd, cgi + +.. contents:: Table of Contents + +Description +=========== + +CGI programs allow you to enhance the functionality of the server in a very +straight and simple wait. ... + +Options +======= + +cgi.assign + + file-extensions that are handled by a CGI program + + e.g.: :: + + cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl" ) + +Examples +======== + +To setup a executable which doesn't need the help of a external program you +just don't specify a handler for the extension. :: + + cgi.assign = ( ".sh" => "" ) + +If the file has no extension keep in mind that lighttpd matches not the +extension itself but the right part of the URL: :: + + cgi.assign = ( "/testfile" => "" ) diff --git a/doc/compress.txt b/doc/compress.txt new file mode 100644 index 00000000..7b083e94 --- /dev/null +++ b/doc/compress.txt @@ -0,0 +1,66 @@ +================== +Output Compression +================== + +-------------------- +Module: mod_compress +-------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + a nice, short abstrace about the module + +.. meta:: + :keywords: lighttpd, compress + +.. contents:: Table of Contents + +Description +=========== + +Output compression reduces the network load and can improve the overall +throughput of the webserver. + +Only static content is supported up to now. + +The server negotiates automaticly which compression method is used. +Supported are gzip, deflate, bzip. + +Options +======= + +compress.cache-dir + name of the directory where compressed content will be cached + + e.g.: :: + + compress.cache-dir = "/var/www/cache/" + + # even better with virt-hosting + $HTTP["host"] == "docs.example.org" { + compress.cache-dir = "/var/www/cache/docs.example.org/" + } + + Default: not set, compress the file for every request + +compress.filetype + mimetypes where might get compressed + + e.g.: :: + + compress.filetype = ("text/plain", "text/html") + + Default: not set + + +Compressing Dynamic Content +=========================== + +To compress dynamic content with PHP please enable :: + + zlib.output_compression = 1 + +in the php.ini as PHP provides compression support by itself. diff --git a/doc/configuration.txt b/doc/configuration.txt new file mode 100644 index 00000000..b3a7a61e --- /dev/null +++ b/doc/configuration.txt @@ -0,0 +1,351 @@ +================= +Configuation File +================= + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date$ +:Revision: $Revision$ + +:abstract: + the layout of the configuration file + +.. meta:: + :keywords: lighttpd, configuration + +.. contents:: Table of Contents + +Description +=========== + +Basic Syntax +------------ + +A BNF like notation: :: + + option : NAME = VARIABLE + NAME : modulename.key + VARIABLE : ( <string> | <integer> | <boolean> | <array> ) + <string> : "text" + <integer>: digit* + <boolean>: ( "enable" | "disable" ) + <array> : "(" [ <string> "=>" ] <variable> [, [ <string> "=>" ] <variable> ]* ")" + +Example +------- + +:: + + # default document-root + server.document-root = "/var/www/example.org/pages/" + + # TCP port + server.port = 80 + + # selecting modules + server.modules = ( "mod_access", "mod_rewrite" ) + + # enable directory listings + server.dir-listing = "enable" + + + +Conditional Configuration +========================= + +Most options can be configured conditionally by using the following syntax +(including nesting). + +:: + + <field> <operator> <value> { + ... + } + +where <field> is one of one of the following: + +$HTTP["cookie"] + match on cookie +$HTTP["host"] + match on host +$HTTP["useragent"] + match on useragent +$HTTP["referer"] + match on referer +$HTTP["url"] + match on url +$SERVER["socket"] + match on socket. Value must be on the format "$ip:$port" where $ip is an IP + address and $port a port number. Only equal match (==) is supported. + +<operator> is one of: + +== + string equal match +!= + string not equal match +=~ + perl style regular expression match +!~ + perl style regular expression not match + +and <value> is either a quoted ("") literal string or regular expression. + + +Example +------- + +:: + + # disable directory-listings for /download/* + server.dir-listing = "enable" + $HTTP["url"] =~ "^/download/" { + server.dir-listing = "disable" + } + + # handish virtual hosting + # map all subdomains to a single document-root + $HTTP["host"] =~ "\.example\.org$" { + server.document-root = "/var/www/htdocs/example.org/pages/" + } + + # multiple sockets + $SERVER["socket"] == "127.0.0.1:81" { + server.document-root = "..." + } + + $SERVER["socket"] == "127.0.0.1:443" { + ssl.pemfile = "/var/www/certs/localhost.pem" + ssl.engine = "enable" + + server.document-root = "/var/www/htdocs/secure.example.org/pages/" + } + + # deny access for all googlebot + $HTTP["useragent"] =~ "Google" { + url.access-deny = ( "" ) + } + + # deny access for all image stealers + $HTTP["referer"] !~ "^($|http://www\.example\.org)" { + url.access-deny = ( ".jpg", ".jpeg", ".png" ) + } + +Options +======= + +server module +------------- + +main sections +````````````` + +server.document-root + document-root of the webserver + +server.bind + hostname of the server + +server.port + tcp-port to bind the server to + if nothing is specified port 80 is used + NOTE: port belows 1024 require root-permissions + +server.use-ipv6 + bind to the IPv6 socket + +server.errorlog + pathname of the error-log + if nothing is specified STDERR is used + +server.chroot + root-directory of the server + +server.username + username used to run the server + NOTE: requires root-permissions + +server.groupname + groupname used to run the server + NOTE: requires root-permissions + +server.dir-listing + enables virtual directory listings if a directory is requested no + index-file was found + +server.follow-symlink + allow to follow-symlinks + + default: enabled + +server.indexfiles + list of files to search for if a directory is requested + e.g.: :: + + server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +server.modules + modules to load + +..note: the order of the modules is somewhat important as the modules are + handled in the way they are specified. mod_rewrite should always be + the first module, mod_accesslog always the last. + + e.g.: :: + + server.modules = ( "mod_rewrite", + "mod_redirect", + "mod_alias", + "mod_access", + "mod_auth", + "mod_status", + "mod_fastcgi", + "mod_proxy", + "mod_simple_vhost", + "mod_evhost", + "mod_userdir", + "mod_cgi", + "mod_compress", + "mod_ssi", + "mod_usertrack", + "mod_expire", + "mod_secdownload", + "mod_rrdtool", + "mod_accesslog" ) + +server.event-handler + set the event handler + + Default: "poll" + +server.pid-file + set the name of the .pid-file where the PID of the server should be placed. + This option is used in combination with a start-script and the deamon mode + + Default: not set + +server.max-request-size + maximum size in kbytes of the request (header + body) + + Default: 2Gb + +server.max-worker + number of worker processes to spawn (works but has no benefit) + + Default: 0 + +server.name + name of the server/virtual server + + Default: hostname + +server.max-keep-alive-requests + maximum number of request within a keep-alive session before the server + terminates the connection + + Default: 128 + +server.max-keep-alive-idle + maximum number of seconds until a idling keep-alive connection is droped + + Default: 30 + +server.max-read-idle + maximum number of seconds until a waiting, non keep-alive read times out + and closes the connection + + Default: 60 + +server.max-write-idle + maximum number of seconds until a waiting write call times out and closes + the connection + + Default: 360 + +server.error-handler-404 + uri to call if the requested file results in a 404 + + Default: not set + + Example: :: + + server.error-handler-404 = "/error-404.php" + +server.allow_http11 + defines if HTTP/1.1 is allowed or not. + + Default: enabled + +SSL engine +`````````` + +ssl.pemfile + path to the PEM file for SSL support + +debugging +````````` + +debug.dump-unknown-headers + enables listing of internally unhandled HTTP-headers + + e.g. :: + + debug.dump-unknown-headers = "enable" + +mimetypes +````````` + +mimetype.assign + list of known mimetype mappings + NOTE: if no mapping is given "application/octet-stream" is used + + e.g.: :: + + mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".html" => "text/html", + ".txt" => "text/plain" ) + + +mimetype.use-xattr + If available, use the XFS-style extended attribute interface to + retrieve the "Content-Type" attribute on each file, and use that as the + mime type. If it's not defined or not available, fall back to the + mimetype.assign assignment. + + e.g.: :: + + mimetype.use-xattr = "enable" + + on shell use: + + $ attr -s Content-Type -V image/svg svgfile.svg + + or + + $ attr -s Content-Type -V text/html indexfile + + +debugging +````````` + +debug.log-request-header + + default: disabled + +debug.log-response-header + + default: disabled + +debug.log-file-not-found + + default: disabled + +debug.log-request-handler + + default: disabled diff --git a/doc/fastcgi-state.dot b/doc/fastcgi-state.dot new file mode 100644 index 00000000..8e3068cd --- /dev/null +++ b/doc/fastcgi-state.dot @@ -0,0 +1,6 @@ +digraph fcgistate { + init -> connect -> prepwrite -> write -> read -> close + write -> write + read -> read + connect -> connect +} diff --git a/doc/fastcgi-state.txt b/doc/fastcgi-state.txt new file mode 100644 index 00000000..9e76a6f6 --- /dev/null +++ b/doc/fastcgi-state.txt @@ -0,0 +1,51 @@ +================= +FastCGI Internals +================= + +--------------- +Module: fastcgi +--------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/01 07:01:29 $ +:Revision: $Revision: 1.1 $ + +:abstract: + This is a short summary of the state-engine which is driving the FastCGI + module. It describes the basic concepts and the way the different parts + of the module are connected. + +.. meta:: + :keywords: lighttpd, state-engine, fastcgi + +.. contents:: Table of Contents + +Description +=========== + +States +------ + +The state-engine is currently made of 6 states which are walk-through on +the way each connection. + +:init: + prepare fastcgi-connection +:connect: + waiting for a connection +:prepwrite: + build the fastcgi-request +:write: + write the fastcgi-request to the network +:read: + read fastcgi-response from network and push it to the write-queue +:close: + terminate the connection + +.. image:: fastcgi-state.png + +Delays +------ + +connect, write and read may need to wait for an fdevent. That's the reason +for the loop in the state-diagram. diff --git a/doc/fastcgi.txt b/doc/fastcgi.txt new file mode 100644 index 00000000..7f2422cf --- /dev/null +++ b/doc/fastcgi.txt @@ -0,0 +1,600 @@ +===================== +the FastCGI Interface +===================== + +------------------- +Module: mod_fastcgi +------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.3 $ + +:abstract: + The FastCGI interface is the fastest and most secure way + to interface external process-handlers like Perl, PHP and + you self-written applications. + +.. meta:: + :keywords: lighttpd, FastCGI + +.. contents:: Table of Contents + +Description +=========== + +lighttpd provides an interface to a external programs that +support the FastCGI interface. The FastCGI Interface is +defined by http://www.fastcgi.com/ and is a +platform-indepentant and server independant interface between +a web-application and a webserver. + +This means that FastCGI programs that run with the Apache +Webserver will run seamlessly with lighttpd and vice versa. + + +FastCGI +------- + +FastCGI is removes a lot of the limitations of CGI programs. +CGI programs have the problem that they have to be restarted +by the webserver for every request which leads to really bad +performance values. + +FastCGI removes this limitation by keeping the process running +and handling the requests by this always running process. This +removes the time used for the fork() and the overall startup +and cleanup time which is necessary to create and destroy a +process. + +While CGI programs communicate to the server over pipes, +FastCGI processes use Unix-Domain-Sockets or TCP/IP to talk +with the webserver. This gives you the second advantage over +simple CGI programs: FastCGI don't have to run on the Webserver +itself but everywhere in the network. + +lighttpd takes it a little bit further by providing a internal +FastCGI load-balancer which can be used to balance the load +over multiple FastCGI Servers. In contrast to other solutions +only the FastCGI process has to be on the cluster and not the +whole webserver. That gives the FastCGI process more resources +than a e.g. load-balancer+apache+mod_php solution. + +If you compare FastCGI against a apache+mod_php solution you +should note that FastCGI provides additional security as the +FastCGI process can be run under different permissions that +the webserver and can also live a chroot which might be +different than the one the webserver is running in. + +Options +======= + +lighttpd provides the FastCGI support via the fastcgi-module +(mod_fastcgi) which provides 2 options in the config-file: + +fastcgi.debug + a value between 0 and 65535 to set the debug-level in the + FastCGI module. Currently only 0 and 1 are used. Use 1 to + enable some debug output, 0 to disable it. + +fastcgi.server + tell the module where to send FastCGI requests to. Every + file-extension can have it own handler. Load-Balancing is + done by specifying multiple handles for the same extension. + + structure of fastcgi.server section: :: + + ( <extension> => + ( <handle> => + ( "host" => <string> , + "port" => <integer> , + "socket" => <string>, # either socket + # or host+port + "bin-path" => <string>, # OPTIONAL + "bin-environment" => <array>, # OPTIONAL + "bin-copy-environment" => <array>, # OPTIONAL + "mode" => <string>, # OPTIONAL + "docroot" => <string> , # OPTIONAL if "mode" + # is not "authorizer" + "check-local" => <string>, # OPTIONAL + "min-procs" => <integer>, # OPTIONAL + "max-procs" => <integer>, # OPTIONAL + "max-load-per-proc" => <integer>, # OPTIONAL + "idle-timeout" => <integer> # OPTIONAL + ) + ), + ( <handle> => ... + ) + ) + + :<extension>: is the file-extension or prefix + (if started with "/") + :<handle>: is just a unique handle name + :"host": is hostname/ip of the FastCGI process + :"port": is tcp-port on the "host" used by the FastCGI + process + :"bin-path": path to the local FastCGI binary which should be + started if no local FastCGI is running + :"socket": path to the unix-domain socket + :"mode": is the FastCGI protocol mode. + Default is "responder", also "authorizer" + mode is implemented. + :"docroot": is optional and is the docroot on the remote + host for default "responder" mode. For + "authorizer" mode it is MANDATORY and it points + to docroot for authorized requests. For security + reasons it is recommended to keep this docroot + outside of server.document-root tree. + :"check-local": is optional and may be "enable" (default) or + "disable". If enabled the server first check + for a file in local server.document-root tree + and return 404 (Not Found) if no such file. + If disabled, the server forward request to + FastCGI interface without this check. + + If bin-path is set: + + :"min-procs": sets the minium processes to start + :"max-procs": the upper limit of the processess to start + :"max-load-per-proc": maximum number of waiting processes on + average per process before a new process is + spawned + :"idle-timeout": number of seconds before a unused process + gets terminated + :"bin-environment": put an entry into the environment of + the started process + :"bin-copy-environement": clean up the environment and copy + only the specified entries into the fresh + environment of the spawn process + + +Examples +-------- + + Multiple extensions for the same host :: + + fastcgi.server = ( ".php" => + ( "grisu" => + ( + "host" => "127.0.0.1", + "port" => 1026, + "bin-path" => "/usr/local/bin/php" + ) + ), + ".php4" => + ( "grisu" => + ( + "host" => "127.0.0.1", + "port" => 1026 + ) + ) + ) + + Example with prefix: :: + + fastcgi.server = ( "/remote_scripts" => + ( "fcg" => + ( + "host" => "192.168.0.3", + "port" => 9000, + "check-local" => "disable", + "docroot" => "/" # remote server may use + # it's own docroot + ) + ) + ) + + The request `http://my.host.com/remote_scripts/test.cgi` will + be forwarded to fastcgi server at 192.168.0.3 and the value + "/remote_scripts/test.cgi" will be used for the SCRIPT_NAME + variable. Remote server may prepend it with its own + document root. The handling of index files si also the + resposibility of remote server for this case. + + + Example for "authorizer" mode: :: + + fastcgi.server = ( "/remote_scripts" => + ( "auth" => + ( + "host" => "10.0.0.2", + "port" => 9000, + "docroot" => "/path_to_private_docs", + "mode" => "authorizer" + ) + ) + ) + + Note that if "docroot" is specified then its value will be + used in DOCUMENT_ROOT and SCRIPT_FILENAME variables passed + to FastCGI server. + +Load-Balancing +============== + +The FastCGI plugin provides automaticly a load-balancing between +multiple FastCGI servers. :: + + fastcgi.server = ( ".php" => + ( "server1" => + ( "host" => "10.0.0.3", + "port" => 1030 ), + "server2" => + ( "host" => "10.0.0.3", + "port" => 1030 ) + ) + ) + + + + +To understand how the load-balancing works you can enable the +fastcgi.debug option and will get a similar output as here: :: + + proc: 127.0.0.1 1031 1 1 1 31454 + proc: 127.0.0.1 1028 1 1 1 31442 + proc: 127.0.0.1 1030 1 1 1 31449 + proc: 127.0.0.1 1029 1 1 2 31447 + proc: 127.0.0.1 1026 1 1 2 31438 + got proc: 34 31454 + release proc: 40 31438 + proc: 127.0.0.1 1026 1 1 1 31438 + proc: 127.0.0.1 1028 1 1 1 31442 + proc: 127.0.0.1 1030 1 1 1 31449 + proc: 127.0.0.1 1031 1 1 2 31454 + proc: 127.0.0.1 1029 1 1 2 31447 + +Even if this for multiple FastCGI children on the local machine +the following explaination is valid for remote connections too. + +The output shows: + +- IP, port, unix-socket (is empty here) +- is-local, state (0 - unset, 1 - running, ... ) +- active connections (load) +- PID + +As you can see the list is always sorted by the load field. + +When ever a new connection is requested, the first entry (the one +with the lowest load) is selected, the load is increase (got proc: ...) +and the list is sorted again. + +If a FastCGI request is done or the connection is drop, the load on the +FastCGI proc decreases and the list is sorted again (release proc: ...) + +This behaviour is very light-weight in code and still very efficient +as it keeps the fastcgi-servers equally loaded even if they have different +CPUs. + +Adaptive Process Spawning +========================= + +Starting with 1.3.8 lighttpd can spawn processes on demand if +a bin-path is specified and the FastCGI process runs locally. + +If you want to have a least one FastCGI process running and +more of the number of requests increases you can use min-procs +and max-procs. + +A new process is spawned as soon as the average number of +requests waiting to be handle by a single process increases the +max-load-per-proc setting. + +The idle-timeout specifies how long a fastcgi-process should wait +for a new request before it kills itself. + +Example +------- +:: + + fastcgi.server = ( ".php" => ( "localhost" => + ( "socket" => "/tmp/php.socket", + "bin-path" => "/usr/local/bin/php", + "min-procs" => 1, + "max-procs" => 32, + "max-load-per-proc" => 4, + "idle-timeout" => 20 ) + ) ) + +Disabling Adaptive Spawning +--------------------------- + +Adaptive Spawning is a quite new feature and it might misbehave +for your setup. There are several ways to control how the spawing +is done: + +1. ``"max-load-per-proc" => 1`` + if that works for you, great. + +2. If not set ``min-procs == max-procs``. + +3. For PHP you can also use: :: + + $ PHP_FCGI_CHILDREN=384 ./lighttpd -f ./lighttpd.conf + + fastcgi.server = ( ".php" => ( "localhost" => + ( "socket" => "/tmp/php.socket", + "bin-path" => "/usr/local/bin/php", + "min-procs" => 1, + "max-procs" => 1, + "max-load-per-proc" => 4, + "idle-timeout" => 20 ) + ) ) + + It will create one socket and let's PHP create the 384 processes itself. + +4. If you don't want lighttpd to manage the fastcgi processes, remove the + bin-path and use spawn-fcgi to spawn them itself. + + +FastCGI and Programming Languages +================================= + +Preparing PHP as a FastCGI program +---------------------------------- + +One of the most important application that has a FastCGI +interface is php which can be downloaded from +http://www.php.net/ . You have to recompile the php from +source to enable the FastCGI interface as it is normally +not enabled by default in the distributions. + +If you already have a working installation of PHP on a +webserver execute a small script which just contains :: + + <?php phpinfo(); ?> + +and search for the line in that contains the configure call. +You can use it as the base for the compilation. + +You have to remove all occurences of `--with-apxs`, `--with-apxs2` +and the like which would build PHP with Apache support. Add the +next three switches to compile PHP with FastCGI support:: + + $ ./configure \ + --enable-fastcgi \ + --enable-discard-path \ + --enable-force-cgi-redirect \ + ... + +After compilation and installation check that your PHP +binary contains FastCGI support by calling: :: + + $ php -v + PHP 4.3.3RC2-dev (cgi-fcgi) (built: Oct 19 2003 23:19:17) + +The important part is the (cgi-fcgi). + + +Configuring PHP +--------------- + +It is important that the php.ini contains: :: + + cgi.fix_pathinfo = 1 + +Otherwise PHP_SELF won't be set correctly. + +Starting a FastCGI-PHP +---------------------- + +Starting with version 1.3.6 lighttpd can spawn the FastCGI +processes locally itself if necessary: :: + + fastcgi.server = ( ".php" => + ( "localhost" => + ( "socket" => "/tmp/php-fastcgi.socket", + "bin-path" => "/usr/local/bin/php" + ) + ) + ) + +PHP provides 2 special environment variables which control the number of +spawned workes under the control of a single watching process +(PHP_FCGI_CHILDREN) and the number of requests what a single worker +handles before it kills itself. :: + + fastcgi.server = ( ".php" => + ( "localhost" => + ( "socket" => "/tmp/php-fastcgi.socket", + "bin-path" => "/usr/local/bin/php", + "bin-environment" => ( + "PHP_FCGI_CHILDREN" => "16", + "PHP_FCGI_MAX_REQUESTS" => "10000" + ) + ) + ) + ) + +To increase the security of the started process you should only pass +the necessary environment variables to the FastCGI process. :: + + fastcgi.server = ( ".php" => + ( "localhost" => + ( "socket" => "/tmp/php-fastcgi.socket", + "bin-path" => "/usr/local/bin/php", + "bin-environment" => ( + "PHP_FCGI_CHILDREN" => "16", + "PHP_FCGI_MAX_REQUESTS" => "10000" + ), + "bin-copy-environment" => ( + "PATH", "SHELL", "USER" + ) + ) + ) + ) + + +External Spawning +----------------- + +Spawning FastCGI processes directly in the webserver has some +disadvantages like + +- FastCGI process can only run locally +- has the same permissions as the webserver +- has the same base-dir as the webserver + +As soon as you are using a seperate FastCGI Server to +take off some load from the webserver you have to control +the FastCGI process by a external program like spawn-fcgi. + +spawn-fcgi is used to start a FastCGI process in its own +environment and set the user-id, group-id and change to +another root-directory (chroot). + +For convenience a wrapper script should be used which takes +care of all the necessary option. Such a script in included +in the lighttpd distribution and is call spawn-php.sh. + +The script has a set of config variables you should take +a look at: :: + + ## ABSOLUTE path to the spawn-fcgi binary + SPAWNFCGI="/usr/local/sbin/spawn-fcgi" + + ## ABSOLUTE path to the PHP binary + FCGIPROGRAM="/usr/local/bin/php" + + ## bind to tcp-port on localhost + FCGIPORT="1026" + + ## bind to unix domain socket + # FCGISOCKET="/tmp/php.sock" + + ## number of PHP childs to spawn + PHP_FCGI_CHILDREN=10 + + ## number of request server by a single php-process until + ## is will be restarted + PHP_FCGI_MAX_REQUESTS=1000 + + ## IP adresses where PHP should access server connections + ## from + FCGI_WEB_SERVER_ADDRS="127.0.0.1,192.168.0.1" + + # allowed environment variables sperated by spaces + ALLOWED_ENV="ORACLE_HOME PATH USER" + + ## if this script is run as root switch to the following user + USERID=wwwrun + GROUPID=wwwrun + +If you have set the variables to values that fit to your +setup you can start it by calling: :: + + $ spawn-php.sh + spawn-fcgi.c.136: child spawned successfully: PID: 6925 + +If you get "child spawned successfully: PID:" the php +processes could be started successfully. You should see them +in your processlist: :: + + $ ps ax | grep php + 6925 ? S 0:00 /usr/local/bin/php + 6928 ? S 0:00 /usr/local/bin/php + ... + +The number of processes should be PHP_FCGI_CHILDREN + 1. +Here the process 6925 is the master of the slaves which +handle the work in parallel. Number of parallel workers can +be set by PHP_FCGI_CHILDREN. A worker dies automaticly of +handling PHP_FCGI_MAX_REQUESTS requests as PHP might have +memory leaks. + +If you start the script as user root php processes will be +running as the user USERID and group GROUPID to drop the +root permissions. Otherwise the php processes will run as +the user you started script as. + +As the script might be started from a unknown stage or even +directly from the command-line it cleans the environment +before starting the processes. ALLOWED_ENV contains all +the external environement variables that should be available +to the php-process. + + + + +Perl +---- + +For Perl you have to install the FCGI module from CPAN. + +TCL +--- + +For TCL ... + + + +Skeleton for remote authorizer +============================== + +The basic functionality of authorizer is as follows (see +http://www.fastcgi.com/devkit/doc/fcgi-spec.html, 6.3 for +details). :: + + #include <fcgi_stdio.h> + #include <stdlib.h> + #include <unistd.h> + int main () { + char* p; + + while (FCGI_Accept() >= 0) { + /* wait for fastcgi authorizer request */ + + printf("Content-type: text/html\r\n"); + + if ((p = getenv("QUERY_STRING")) == NULL) || + <QUERY_STRING is unauthorized>) + printf("Status: 403 Forbidden\r\n\r\n"); + + else printf("\r\n"); + /* default Status is 200 - allow access */ + } + + return 0; + } + +It is possible to use any other variables provided by +FastCGI interface for authorization check. Here is only an +example. + + +Troubleshooting +=============== + +fastcgi.debug should be enabled for troubleshooting. + +If you get: :: + + (fcgi.c.274) connect delayed: 8 + (fcgi.c.289) connect succeeded: 8 + (fcgi.c.745) unexpected end-of-file (perhaps the fastcgi + process died): 8 + +the fastcgi process accepted the connection but closed it +right away. This happens if FCGI_WEB_SERVER_ADDRS doesn't +include the host where you are connection from. + +If you get :: + + (fcgi.c.274) connect delayed: 7 + (fcgi.c.1107) error: unexpected close of fastcgi connection + for /peterp/seite1.php (no fastcgi process on host/port ?) + (fcgi.c.1015) emergency exit: fastcgi: connection-fd: 5 + fcgi-fd: 7 + +the fastcgi process is not running on the host/port you are +connection to. Check your configuration. + +If you get :: + + (fcgi.c.274) connect delayed: 7 + (fcgi.c.289) connect succeeded: 7 + +everything is fine. The connect() call just was delayed a +little bit and is completly normal. + diff --git a/doc/features.txt b/doc/features.txt new file mode 100644 index 00000000..b5885463 --- /dev/null +++ b/doc/features.txt @@ -0,0 +1,116 @@ +=============== +progress report +=============== + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + This document tries to track the requested features and + the release when they have been implemented. + +.. meta:: + :keywords: lighttpd, features + +.. contents:: Table of Contents + +Description +=========== + +The document was inspired by a mail from David Phillips: + +http://marc.theaimsgroup.com/?l=thttpd&m=108051453226692&w=2 + +It is used to see what is still missing and what is already done. :: + + zell@zell.best.vwh.net writes: + > Now that the author has made the source code available, I am + > considering installing and testing the latest version. From a + > quick glance, it seems to support most/all of the features of + > Premium thttpd and Zeus. + + If you think it compares to Zeus, then you've obviously never used Zeus. + + lighttpd is currently the only non-blocking open source web server to + support FastCGI responders and that's worthwhile. + + The documentation is lacking. Comments in the configuration file do not + make up for a complete manual. + +Constantly improving. :: + + The configuration syntax is overly complex, like Apache. There no .htaccess + support. + +.htaccess support is not planed yet. :: + + There is only one server. You cannot have a separate configuration for each + virtual server. This would seem to be especially problamatic when doing + SSL. + +Works since 1.3.0. :: + + There is no SSI support. Zeus has full recursive SSI support. Output from + a FastCGI program can get run through the SSI interpreter. SSI can also do + virtual includes recursively. + +SSI works since 1.2.4. :: + + Request logging is not configurable. Zeus supports fully configurable + access logging, plus a binary version of CLF that save space. + +1.2.6 adds Apache-like logfile config. :: + + Access control only allows authentication via username and password. There + is no way to allow or deny based in IP address. + +planed for 1.3.x :: + + The request rewriting appears to only allow regex substitutions. Zeus has a + simple, yet powerful, request rewrite language. + + + + There is no support for FastCGI authorizers. These are very useful for high + traffic sites that require complex authentication schemes or that store + authorization information in a central database. + +since 1.1.9. :: + + There is no bandwidth throttling support. Zeus does bandwidth throttling + correctly (i.e. unlike past versions of thttpd) and can throttle on a + per-subserver (thttpd-style virtual hosts) basis. + +since 1.3.8. :: + + There is no ISAPI support. ISAPI is an elegant, open API that allows + modification of web server behavior. While it isn't strictly necessary for + an open source web server, it nice to have a documented, consistent API, + rather than having to manually patch the server. + +If someone requests it it might be implemented. :: + + There is no web based interface. Zeus has a complete web based interface + for everything, including a powerful feature of configuring multiple virtual + servers at once. + +That is something that should be a special feature of Zeus. :) :: + + There is no support for mapping certain URLs to specific filesystem paths. + +since 1.2.6 :: + + There is no referring checking. This is incredibly important to prevent + hotlinking of bandwidth intensive media types (images, movies, etc.). + +we have something better: mod_secdownload. And if someone wants referer +checking we have a condition in the config for it since 1.2.9 :: + + Zeus has a lot of features that lighttpd doesn't have, but I only mentioned + the ones I care about and use. + + -- + David Phillips <david@acz.org> + http://david.acz.org/ + diff --git a/doc/lighttpd.1 b/doc/lighttpd.1 new file mode 100644 index 00000000..1bfd2c54 --- /dev/null +++ b/doc/lighttpd.1 @@ -0,0 +1,18 @@ +.TH LIGHTTPD 1 2003-12-21 +.SH NAME +lighttpd - a fast, secure and flexible webserver +.SH SYNOPSIS +lighttpd -D -f <configfile> +.SH DESCRIPTION +.SH FILES +/etc/lighttpd/lighttpd.conf +.SH CONFORMING TO +HTTP/1.0 +HTTP/1.0 +HTTP-Authentification - Basic, Digest +FastCGI +CGI/1.1 +.SH SEE ALSO +spawn-fcgi(1) +.SH AUTHOR +jan.kneschke@incremental.de diff --git a/doc/lighttpd.conf b/doc/lighttpd.conf new file mode 100644 index 00000000..e5c29b98 --- /dev/null +++ b/doc/lighttpd.conf @@ -0,0 +1,264 @@ +# lighttpd configuration file +# +# use a it as base for lighttpd 1.0.0 and above +# +# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $ + +############ Options you really have to take care of #################### + +## modules to load +# at least mod_access and mod_accesslog should be loaded +# all other module should only be loaded if really neccesary +# - saves some time +# - saves memory +server.modules = ( +# "mod_rewrite", +# "mod_redirect", +# "mod_alias", + "mod_access", +# "mod_auth", +# "mod_status", +# "mod_setenv", +# "mod_fastcgi", +# "mod_proxy", +# "mod_simple_vhost", +# "mod_evhost", +# "mod_userdir", +# "mod_cgi", +# "mod_compress", +# "mod_ssi", +# "mod_usertrack", +# "mod_expire", +# "mod_secdownload", +# "mod_rrdtool", + "mod_accesslog" ) + +## a static document-root, for virtual-hosting take look at the +## server.virtual-* options +server.document-root = "/www/pages/" + +## where to send error-messages to +server.errorlog = "/www/logs/lighttpd.error.log" + +# files to check for if .../ is requested +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +# mimetype mapping +mimetype.assign = ( + ".pdf" => "application/pdf", + ".sig" => "application/pgp-signature", + ".spl" => "application/futuresplash", + ".class" => "application/octet-stream", + ".ps" => "application/postscript", + ".torrent" => "application/x-bittorrent", + ".dvi" => "application/x-dvi", + ".gz" => "application/x-gzip", + ".pac" => "application/x-ns-proxy-autoconfig", + ".swf" => "application/x-shockwave-flash", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".tar" => "application/x-tar", + ".zip" => "application/zip", + ".mp3" => "audio/mpeg", + ".m3u" => "audio/x-mpegurl", + ".wma" => "audio/x-ms-wma", + ".wax" => "audio/x-ms-wax", + ".ogg" => "audio/x-wav", + ".wav" => "audio/x-wav", + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".xbm" => "image/x-xbitmap", + ".xpm" => "image/x-xpixmap", + ".xwd" => "image/x-xwindowdump", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".js" => "text/javascript", + ".asc" => "text/plain", + ".c" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mov" => "video/quicktime", + ".qt" => "video/quicktime", + ".avi" => "video/x-msvideo", + ".asf" => "video/x-ms-asf", + ".asx" => "video/x-ms-asf", + ".wmv" => "video/x-ms-wmv" + ) + +# Use the "Content-Type" extended attribute to obtain mime type if possible +#mimetypes.use-xattr = "enable" + + +## send a different Server: header +## be nice and keep it at lighttpd +#server.tag = "lighttpd" + +#### accesslog module +accesslog.filename = "/www/logs/access.log" + +## deny access the file-extensions +# +# ~ is for backupfiles from vi, emacs, joe, ... +# .inc is often used for code includes which should in general not be part +# of the document-root +url.access-deny = ( "~", ".inc" ) + + + +######### Options that are good to be but not neccesary to be changed ####### + +## bind to port (default: 80) +#server.port = 81 + +## bind to localhost (default: all interfaces) +#server.bind = "grisu.home.kneschke.de" + +## error-handler for status 404 +#server.error-handler-404 = "/error-handler.html" +#server.error-handler-404 = "/error-handler.php" + +## to help the rc.scripts +#server.pid-file = "/var/run/lighttpd.pid" + + +###### virtual hosts +## +## If you want name-based virtual hosting add the next three settings and load +## mod_simple_vhost +## +## document-root = +## virtual-server-root + virtual-server-default-host + virtual-server-docroot +## or +## virtual-server-root + http-host + virtual-server-docroot +## +#simple-vhost.server-root = "/home/weigon/wwwroot/servers/" +#simple-vhost.default-host = "grisu.home.kneschke.de" +#simple-vhost.document-root = "/pages/" + + +## +## Format: <errorfile-prefix><status-code>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +## virtual directory listings +#server.dir-listing = "enable" + +## enable debugging +#debug.log-request-header = "enable" +#debug.log-response-header = "enable" +#debug.log-request-handling = "enable" +#debug.log-file-not-found = "enable" + +### only root can use these options +# +# chroot() to directory (default: no chroot() ) +#server.chroot = "/" + +## change uid to <uid> (default: don't care) +#server.username = "wwwrun" + +## change uid to <uid> (default: don't care) +#server.groupname = "wwwrun" + +#### compress module +#compress.cache-dir = "/tmp/lighttpd/cache/compress/" +#compress.filetype = ("text/plain", "text/html") + +#### proxy module +## read proxy.txt for more info +#proxy.server = ( ".php" => +# ( "localhost" => +# ( +# "host" => "192.168.0.101", +# "port" => 80 +# ) +# ) +# ) + +#### fastcgi module +## read fastcgi.txt for more info +#fastcgi.server = ( ".php" => +# ( "localhost" => +# ( +# "socket" => "/tmp/php-fastcgi.socket", +# "bin-path" => "/usr/local/bin/php" +# ) +# ) +# ) + +#### CGI module +#cgi.assign = ( ".pl" => "/usr/bin/perl", +# ".cgi" => "/usr/bin/perl" ) +# + +#### SSL engine +#ssl.engine = "enable" +#ssl.pemfile = "server.pem" + +#### status module +#status.status-url = "/server-status" +#status.config-url = "/server-config" + +#### auth module +## read authentification.txt for more info +#auth.backend = "plain" +#auth.backend.plain.userfile = "lighttpd.user" +#auth.backend.plain.groupfile = "lighttpd.group" + +#auth.backend.ldap.hostname = "localhost" +#auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +#auth.backend.ldap.filter = "(uid=$)" + +#auth.require = ( "/server-status" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "group=www|user=jan|host=192.168.2.10" +# ), +# "/server-info" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "group=www|user=jan|host=192.168.2.10" +# ) +# ) + +#### url handling modules (rewrite, redirect, access) +#url.rewrite = ( "^/$" => "/server-status" ) +#url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" ) + +# +# 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/storage/dev/www/%3/htdocs/" + +#### expire module +#expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#### ssi +#ssi.extension = ( ".shtml" ) + +#### rrdtool +#rrdtool.binary = "/usr/bin/rrdtool" +#rrdtool.db-name = "/var/www/lighttpd.rrd" + +#### setenv +#setenv.add-request-header = ( "TRAV_ENV" => "mysql://user@host/db" ) +#setenv.add-response-header = ( "X-Secret-Message" => "42" ) + diff --git a/doc/lighttpd.user b/doc/lighttpd.user new file mode 100644 index 00000000..727e9c33 --- /dev/null +++ b/doc/lighttpd.user @@ -0,0 +1 @@ +dummy:test123 diff --git a/doc/mysqlvhost.txt b/doc/mysqlvhost.txt new file mode 100644 index 00000000..db7edd0c --- /dev/null +++ b/doc/mysqlvhost.txt @@ -0,0 +1,86 @@ + +==================== +MySQL bases vhosting +==================== + +----------------------- +Module: mod_mysql_vhost +----------------------- + +:Author: ada@riksnet.se +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + This module provides virtual hosts (vhosts) based on a MySQL table, and + (optionally) prepares mod_fastcgi for sub-chroot FastCGI deployment. + +.. meta:: + :keywords: lighttpd, mysql, vhost + +.. contents:: Table of Contents + +Description +=========== + +vhost via mysql + +Options +======= + +Example: :: + + mysql-vhost.db = "lighttpd" + mysql-vhost.user = "lighttpd" + mysql-vhost.pass = "secret" + mysql-vhost.sock = "/var/mysql.lighttpd.sock" + mysql-vhost.sql = "SELECT docroot,fcgioffset,fcgiarg FROM domains WHERE domain='?'" + + +MySQL setup: :: + + GRANT SELECT ON lighttpd.* TO lighttpd@localhost IDENTIFIED BY 'secret'; + + CREATE DATABASE lighttpd; + + USE lighttpd; + + CREATE TABLE domains ( + domain char(64) not null primary key, + docroot char(128) not null, + fcgioffset tinyint unsigned not null, + fcgiarg smallint unsigned not null + ); + + INSERT INTO domains VALUES ('host.dom.ain','/http/host.dom.ain/',5,10001); + + +Extra stuff: :: + + fastcgi.server = ( ".php" => ( "php-fcgi" => ( + "socket" => "../php/socket", + "spawn" => "/php/php-spawn" + ))) + + +The example above will get the docroot for a virtual host from the +lighttpd.domains table, and also change the docroot for FastCGI to +be 5 (=fcgioffset) chars below the web docroot (in the example above +this means that the web docroot is /http/host.dom.ain/, while the +FastCGI docroot will be /host.dom.ain/). + +The fastcgi.server "socket" is (patched to be) relative to docroot, +and the new "spawn" argument specifies a command to run to dynamically +create a new FastCGI process in case none is running (as opposed to +"bin-path" which does not dynamically create/restart FastCGI processes). + +The example above will also add the argument "10001" (=fcgiarg) when +invoking the /php/php-spawn (=spawn) FastCGI spawn program. Tf the +spawn program is setuid this can be used to chroot and setgid/setuid +to the right user before exec:ing the actual FastCGI program. + +NOTE: both fcgioffset and fcgiarg are optional. Just remove every +mention of them in the example above for a pure vhost server. +You can also use just fcgioffset and not fcgiarg if you like. + + diff --git a/doc/performance.txt b/doc/performance.txt new file mode 100644 index 00000000..3d0eff90 --- /dev/null +++ b/doc/performance.txt @@ -0,0 +1,198 @@ +======================== +Performance Improvements +======================== + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.3 $ + +:abstract: + handling performance issues in lighttpd + +.. meta:: + :keywords: lighttpd, performance + +.. contents:: Table of Contents + +Description +=========== + +Performance Issues +------------------ + +lighttpd is optimized into various directions. The most important is +performance. The operation system has two major facalities to help lighttpd +a deliver it best performance. + +HTTP Keep-Alive +--------------- + +Disabling keep-alive might help your server if you suffer from a large +number of open file-descriptors. + +:: + + server.max-keep-alive-requests = 128 + server.max-keep-alive-idle = 30 + server.max-read-idle = 60 + server.max-write-idle = 360 + +Event Handlers +-------------- + +The first one is the Event Handler which cares about notifying the server +that one of the connections is ready to send or to recieve. As you can see +every OS has at least the select() call which has some limitations. + +============ ========== =============== +OS Method Config-Value +============ ========== =============== +all select select +Unix poll poll +Linux 2.4+ rt-signals linux-rtsig +Linux 2.6+ epoll linux-sysepoll +Solaris /dev/poll solaris-devpoll +FreeBSD, ... kqueue freebsd-kqueue +============ ========== =============== + + +For more infomation in this topic take a look at http://www.kegel.com/c10k.html + +Configuration +````````````` + +The event-handler can be set by specifying the 'Config-Value' from above +in the ``server.event-handler`` variable + +e.g.: :: + + server.event-handler = "linux-sysepoll" + +Network Handlers +---------------- + +The basic network interface for all platforms at the syscalls read() and +write(). Each modern OS provides its own syscall to help network servers +to transfer files as fast as possible. + +If you want to send out a file from the webserver it does make any sense +to copy the file into the webserver just to write() it back into a socket +in the next step. + +sendfile() minimizes the work in the application and pushes a file directly +into the network card (idealy spoken). + +lighttpd supports all major platform specific calls: + +========== ========== +OS Method +========== ========== +all write +Unix writev +Linux 2.4+ sendfile +Linux 2.6+ sendfile64 +Solaris sendfilev +FreeBSD sendfile +========== ========== + +They are selected automaticly on compile-time. If you have problems check +./src/network_backend.h and disable the corresponding USE\_... define. + +Max Connections +--------------- + +As lighttpd is a single-threaded server its main resource limit is the +number of file-descriptors which is (on most systems) set to 1024 by default. + +If you are running a high-traffic site you might want to increase this limit +by setting :: + + server.max-fds = 2048 + +This only works if lighttpd is started as root. + +Out-of-fd condition +------------------- + +As fds are used for tcp/ip sockets, files, directories, ... a simple request +for a PHP page might result in using 3 fds: + +1. the TCP/IP socket to the client +2. the TCP/IP and Unix domain socket to the FastCGI process +3. the filehandle to the file in the document-root to check if it is really existing + +If lighttpd runs out of file-descriptors it will stop accepting new +connections for while to use the currently available fds (file-descriptors) +to handle the currently running requests. + +If more than 90% of the fds are used the the handling of new connections is +disabled, if it dropes below 80% again new connection will accepted again. + +Under some circumstances you will see :: + + ... accept() failed: Too many open files + +in the error-log. This tells you the you had to many new requests at once +and lighttpd could not disable the incomming connections soon enough. The +connection is drop and the client will get a error-message like 'connection +failed'. This is very rare and might only occur in test-setups. + +Increasing the ``server.max-fds`` limit will reduce the propability of this +problem. + + + + +Plattform Specific Notes +======================== + +Linux +----- + +For Linux 2.4.x should should think about compiling lighttpd with the option +``--disable-lfs`` to disable the support for files larger than 2Gb. lighttpd will +fall back to the ``writev() + mmap()`` network calls which is ok, but not as +fast as possible but support files larger than 2Gb. + +Disabling the TCP options reduces the overhead of each TCP packet and might +help to get the last few percent of performance out of the server. + +- net.ipv4.tcp_sack = 0 +- net.ipv4.tcp_timestamps = 0 + +Increasing the TCP send and receive buffers will increase the performance a +lot if (and only if) you have a lot large files to send. + +- net.ipv4.tcp_wmem = 4096 65536 524288 +- net.core.wmem_max = 1048576 + +If you have a lot large file uploads increasing the receive buffers will help. + +- net.ipv4.tcp_rmem = 4096 87380 524288 +- net.core.rmem_max = 1048576 + +Keep in mind that the buffers have to multiplied by server.max-fds and be +allocated in the Kernel area. Be carefull with that. + +FreeBSD +------- + +On FreeBSD you might gain some performance by enabling accept-filters. Just +compile your kernel with: :: + + options ACCEPT_FILTER_HTTP + +For more ideas in tuning FreeBSD read: tuning(7) + +Reducing the recvspace should always be ok if the server only handles HTTP +requests without large uploads. Increasing the sendspace would reduce the +system-load if you have a lot large files to be sent, but keep in mind that +you to provide the memory in kernel for each connection. 1024 * 64k would mean +64M of kernel-ram. Keep this in mind. + +- net.inet.tcp.recvspace = 4096 + diff --git a/doc/plugins.txt b/doc/plugins.txt new file mode 100644 index 00000000..8a755fea --- /dev/null +++ b/doc/plugins.txt @@ -0,0 +1,260 @@ +================ +Plugin Interface +================ + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/08/01 07:01:29 $ +:Revision: $Revision: 1.1 $ + +:abstract: + The plugin interface is the integral part of lighttpd provide + a flexible way to add specific functionality to lighttpd. + +.. meta:: + :keywords: lighttpd, plugins + +.. contents:: Table of Contents + +Description +=========== + +Plugins allow you to enhance to functionality of lighttpd without +changing the core of the webserver. They can be loaded at startup-time +and can change hardly any aspect of the behaviour of the webserver. + +Plugin Entry Points +------------------- + +lighttpd has 16 hooks which are used in different states of the +execution of the request: + +Serverwide hooks +```````````````` + +:init_: + called when the plugin is loaded +:cleanup_: + called when the plugin is unloaded +:set_defaults_: + called when the configuration has to be processed +:handle_trigger_: + called once a second +:handle_sighup_: + called when the server received a SIGHUP + +Connectionwide hooks +```````````````````` + +Most of these hooks are call in ``http_response_prepare()`` after some +field in the connection structure are set. + +:handle_uri_raw_: + called after uri.path_raw, uri.authority and uri.scheme are set +:handle_uri_clean_: + called after uri.path (a clean uri without .. and %20) is set +:handle_docroot_: + called at the end of the logical path handle to get a docroot +:handle_subrequest_start_: + called if the physical path is setup and checked +:handle_subrequest_: + called at the end of ``http_response_prepare()`` +:handle_physical_path_: + called after the physical path is created and no other handler is + found for this request +:handle_request_done_: + called when the request is done +:handle_connection_close_: + called if the connection has to be closed +:handle_joblist_: + called after the connection_state_engine is left again and plugin + internal handles have to be called +:connection_reset_: + called if the connection structure has to be cleaned up + + +Plugin Interface +---------------- + +\*_plugin_init +`````````````` + +Every plugin has a uniquely named function which is called after the +plugin is loaded. It is used to setup the ``plugin`` structure with +some usefull data: + +- name of the plugin ``name`` +- all hooks + +The field ``data`` and ``lib`` should not be touched in the init function. +``lib`` is the library handler from dlopen and ``data`` will be the storage +of the internal plugin data. + +:returns: + 0 (not handled) + +init +```` + +The first real call of a plugin function is the init-hook which is used +to set up the internal plugin data. The internal plugin is assigned the +``data`` field mentioned in the \*_plugin_init description. + +:returns: + a pointer to the internal plugin data. + +cleanup +``````` + +The cleanup hook is called just before the plugin is unloaded. It is meant +to free all buffers allocated in ``init`` or somewhere else in the plugin +which are still not freed and to close all handles which were opened and +are not closed yet. + +:returns: + HANDLER_GO_ON if ok (not handled) + +set_defaults +```````````` + +set_defaults is your entry point into the configfile parsing. It should +pass a list of options to ``config_insert_values`` and check if +the plugin configuration is valid. If it is not valid yet, it should +set usefull defaults or return with HANDLER_ERROR and an error message. + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR will terminated lighttpd + +connection_reset +```````````````` + +called at the end of each request + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR on error + +handle_trigger +`````````````` + +called once a second + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR on error + +handle_sighup +````````````` + +called if a SIGHUP is received (cycling logfiles, ...) + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR on error + +handle_uri_raw +`````````````` + +called after uri_raw is set + +:returns: + HANDLER_GO_ON if ok + HANDLER_FINISHED if the final output is prepared + + HANDLER_ERROR on error + +handle_uri_clean +```````````````` + +called after uri.path is set + +:returns: + HANDLER_GO_ON if ok + HANDLER_FINISHED if the final output is prepared + + HANDLER_ERROR on error + +handle_docroot +`````````````` + +called when a docroot is needed + +:returns: + HANDLER_GO_ON if ok + HANDLER_FINISHED if the final output is prepared + + HANDLER_ERROR on error + +handle_subrequest_start +``````````````````````` + +called after physical.path is set + +:returns: + HANDLER_GO_ON if ok + HANDLER_FINISHED if the final output is prepared + + HANDLER_ERROR on error + +handle_subrequest +````````````````` + +called if subrequest_start requested a COMEBACK or a WAIT_FOR_EVENT + +:returns: + HANDLER_GO_ON if ok + HANDLER_FINISHED if the final output is prepared + + HANDLER_ERROR on error + +handle_physical_path +```````````````````` + +called after physical.path is set + +:returns: + HANDLER_GO_ON if ok + HANDLER_FINISHED if the final output is prepared + + HANDLER_ERROR on error + + +handle_request_done +``````````````````` + +called at the end of the request (logging, statistics, ...) + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR on error + +handle_connection_close +``````````````````````` + +called if the connection is terminated + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR on error + +handle_joblist +`````````````` + +called if the state of the connection has changed + +:returns: + HANDLER_GO_ON if ok + + HANDLER_ERROR on error + + diff --git a/doc/proxy.txt b/doc/proxy.txt new file mode 100644 index 00000000..6621cf97 --- /dev/null +++ b/doc/proxy.txt @@ -0,0 +1,72 @@ +=================== +the Proxy Interface +=================== + +----------------- +Module: mod_proxy +----------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/01 07:01:29 $ +:Revision: $Revision: 1.1 $ + +:abstract: + The proxy module a simplest way to connect lighttpd to + java servers which have a HTTP-interface. + +.. meta:: + :keywords: lighttpd, Proxy + +.. contents:: Table of Contents + +Description +=========== + +... + +Options +======= + +lighttpd provides the Proxy support via the proxy-module +(mod_proxy) which provides 2 options in the config-file: + +:proxy.debug: + a value between 0 and 65535 to set the debug-level in the + Proxy module. Currently only 0 and 1 are used. Use 1 to + enable some debug output, 0 to disable it. + +:proxy.server: + tell the module where to send Proxy requests to. Every + file-extension can have its own handler. Load-Balancing is + done by specifying multiple handles for the same extension. + + structure of proxy.server section: :: + + ( <extension> => + ( <handle> => + ( "host" => <string> , + "port" => <integer> + ) + ), + ( <handle> => ... + ) + ) + + :<extension>: is the file-extension or prefix + (if started with "/") + :<handle>: is just a unique handle name + :"host": is ip of the proxy server + :"port": is tcp-port on the "host" used by the proxy + server + + e.g.: :: + + proxy.server = ( ".php" => + ( "grisu" => + ( + "host" => "192.168.0.2", + "port" => 1026 + ) + ) + ) + diff --git a/doc/rc.lighttpd b/doc/rc.lighttpd new file mode 100755 index 00000000..7b0255ce --- /dev/null +++ b/doc/rc.lighttpd @@ -0,0 +1,169 @@ +#! /bin/sh +# Copyright (c) 1995-2002 SuSE Linux AG, Nuernberg, Germany. +# All rights reserved. +# +# Author: Kurt Garloff <feedback@suse.de> +# +# /etc/init.d/FOO +# +# and symbolic its link +# +# /(usr/)sbin/rcFOO +# +# LSB compliant service control script; see http://www.linuxbase.org/spec/ +# +# System startup script for some example service or daemon FOO (template) +# +### BEGIN INIT INFO +# Provides: FOO +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Description: Start FOO to allow XY and provide YZ +# continued on second line by '#<TAB>' +### END INIT INFO +# +# Note on Required-Start: It does specify the init script ordering, +# not real dependencies. Depencies have to be handled by admin +# resp. the configuration tools (s)he uses. + +# Source SuSE config (if still necessary, most info has been moved) +test -r /etc/rc.config && . /etc/rc.config + +# Check for missing binaries (stale symlinks should not happen) +LIGHTTPD_BIN=/usr/sbin/lighttpd +test -x $LIGHTTPD_BIN || exit 5 + +# Check for existence of needed config file and read it +LIGHTTPD_CONFIG=/etc/sysconfig/lighttpd +test -r $LIGHTTPD_CONFIG || exit 6 +. $LIGHTTPD_CONFIG + +# Shell functions sourced from /etc/rc.status: +# rc_check check and set local and overall rc status +# rc_status check and set local and overall rc status +# rc_status -v ditto but be verbose in local rc status +# rc_status -v -r ditto and clear the local rc status +# rc_failed set local and overall rc status to failed +# rc_failed <num> set local and overall rc status to <num><num> +# rc_reset clear local rc status (overall remains) +# rc_exit exit appropriate to overall rc status +# rc_active checks whether a service is activated by symlinks +. /etc/rc.status + +# First reset status of this service +rc_reset + +# Return values acc. to LSB for all commands but status: +# 0 - success +# 1 - generic or unspecified error +# 2 - invalid or excess argument(s) +# 3 - unimplemented feature (e.g. "reload") +# 4 - insufficient privilege +# 5 - program is not installed +# 6 - program is not configured +# 7 - program is not running +# +# Note that starting an already running service, stopping +# or restarting a not-running service as well as the restart +# with force-reload (in case signalling is not supported) are +# considered a success. + +case "$1" in + start) + echo -n "Starting lighttpd" + ## Start daemon with startproc(8). If this fails + ## the echo return value is set appropriate. + + # NOTE: startproc returns 0, even if service is + # already running to match LSB spec. + startproc $LIGHTTPD_BIN -f $LIGHTTPD_CONF_PATH + + # Remember status and be verbose + rc_status -v + ;; + stop) + echo -n "Shutting down lighttpd" + ## Stop daemon with killproc(8) and if this fails + ## set echo the echo return value. + + killproc -TERM $LIGHTTPD_BIN + + # Remember status and be verbose + rc_status -v + ;; + try-restart) + ## Stop the service and if this succeeds (i.e. the + ## service was running before), start it again. + ## Note: try-restart is not (yet) part of LSB (as of 0.7.5) + $0 status >/dev/null && $0 restart + + # Remember status and be quiet + rc_status + ;; + restart) + ## Stop the service and regardless of whether it was + ## running or not, start it again. + $0 stop + $0 start + + # Remember status and be quiet + rc_status + ;; + force-reload) + ## Signal the daemon to reload its config. Most daemons + ## do this on signal 1 (SIGHUP). + ## If it does not support it, restart. + + echo -n "Reload service LIGHTTPD" + ## if it supports it: + killproc -HUP $LIGHTTPD_BIN + #touch /var/run/LIGHTTPD.pid + rc_status -v + + ## Otherwise: + #$0 stop && $0 start + #rc_status + ;; + reload) + ## Like force-reload, but if daemon does not support + ## signalling, do nothing (!) + + # If it supports signalling: + echo -n "Reload service LIGHTTPD" + killproc -HUP $LIGHTTPD_BIN + #touch /var/run/LIGHTTPD.pid + rc_status -v + + ## Otherwise if it does not support reload: + #rc_failed 3 + #rc_status -v + ;; + status) + echo -n "Checking for service LIGHTTPD: " + ## Check status with checkproc(8), if process is running + ## checkproc will return with exit status 0. + + # Return value is slightly different for the status command: + # 0 - service running + # 1 - service dead, but /var/run/ pid file exists + # 2 - service dead, but /var/lock/ lock file exists + # 3 - service not running + + # NOTE: checkproc returns LSB compliant status values. + checkproc $LIGHTTPD_BIN + rc_status -v + ;; + probe) + ## Optional: Probe for the necessity of a reload, + ## print out the argument which is required for a reload. + + test /etc/lighttpd/lighttpd.conf -nt /var/run/lighttpd.pid && echo reload + ;; + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" + exit 1 + ;; +esac +rc_exit diff --git a/doc/rc.lighttpd.redhat b/doc/rc.lighttpd.redhat new file mode 100755 index 00000000..e22d551d --- /dev/null +++ b/doc/rc.lighttpd.redhat @@ -0,0 +1,87 @@ +#!/bin/sh +# +# lighttpd Startup script for the lighttpd server +# +# chkconfig: - 85 15 +# description: Lightning fast webserver with light system requirements +# +# processname: lighttpd +# config: /etc/lighttpd/lighttpd.conf +# config: /etc/sysconfig/lighttpd +# pidfile: /var/run/lighttpd.pid +# +# Note: pidfile is assumed to be created +# by lighttpd (config: server.pid-file). +# If not, uncomment 'pidof' line. + +# Source function library +. /etc/rc.d/init.d/functions + +if [ -f /etc/sysconfig/lighttpd ]; then + . /etc/sysconfig/lighttpd +fi + +if [ -z "$LIGHTTPD_CONF_PATH" ]; then + LIGHTTPD_CONF_PATH="/etc/lighttpd/lighttpd.conf" +fi + +prog="lighttpd" +lighttpd="/usr/sbin/lighttpd" +RETVAL=0 + +start() { + echo -n $"Starting $prog: " + daemon $lighttpd -f $LIGHTTPD_CONF_PATH + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + killproc $lighttpd + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog + return $RETVAL +} + +reload() { + echo -n $"Reloading $prog: " + killproc $lighttpd -HUP + RETVAL=$? + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + condrestart) + if [ -f /var/lock/subsys/$prog ]; then + stop + start + fi + ;; + reload) + reload + ;; + status) + status $lighttpd + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|restart|condrestart|reload|status}" + RETVAL=1 +esac + +exit $RETVAL diff --git a/doc/redirect.txt b/doc/redirect.txt new file mode 100644 index 00000000..10c6a16a --- /dev/null +++ b/doc/redirect.txt @@ -0,0 +1,36 @@ +=============== +URL Redirection +=============== + +-------------------- +Module: mod_redirect +-------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + url redirection + +.. meta:: + :keywords: lighttpd, redirect + +.. contents:: Table of Contents + +Description +=========== + +... + +Options +======= + +url.redirect + redirects a set of URLs externally + + e.g. :: + + url.redirect = ( "^/show/([0-9]+)/([0-9]+)$" => "http://www.example.org/show.php?isdn=$1&page$2", + "^/get/([0-9]+)/([0-9]+)$" => "http://www.example.org/get.php?isdn=$1&page$2" ) + diff --git a/doc/rewrite.txt b/doc/rewrite.txt new file mode 100644 index 00000000..f9102642 --- /dev/null +++ b/doc/rewrite.txt @@ -0,0 +1,42 @@ +============ +URL Rewrites +============ + +------------------- +Module: mod_rewrite +------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + url rewrite + +.. meta:: + :keywords: lighttpd, rewrite + +.. contents:: Table of Contents + +Description +=========== + +internal redirects, url rewrite + +Options +======= + +url.rewrite + rewrites a set of URLs interally in the webserver BEFORE they are handled. + + e.g. :: + + url.rewrite = ( "^/show/([0-9]+)/([0-9]+)$" => "/show.php?isdn=$1&page$2" ) + +url.rewrite-final + rewrites a set of URLs interally in the webserver BEFORE they are handled + and rewrite the urls in the second round + + e.g. :: + + url.rewrite-final = ( "^/get/([0-9]+)/([0-9]+)$" => "/get.php?isdn=$1&page$2" ) diff --git a/doc/rrdtool-graph.sh b/doc/rrdtool-graph.sh new file mode 100755 index 00000000..1157a285 --- /dev/null +++ b/doc/rrdtool-graph.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +RRDTOOL=/usr/bin/rrdtool +OUTDIR=/var/www/servers/www.example.org/pages/rrd/ +INFILE=/var/www/lighttpd.rrd +OUTPRE=lighttpd-traffic + +DISP="DEF:bin=$INFILE:InOctets:AVERAGE \ + DEF:binmin=$INFILE:InOctets:MIN \ + DEF:binmax=$INFILE:InOctets:MAX \ + DEF:bout=$INFILE:OutOctets:AVERAGE \ + DEF:boutmin=$INFILE:OutOctets:MIN \ + DEF:boutmax=$INFILE:OutOctets:MAX \ + LINE1:bin#0000FF:in \ + LINE1:binmin#2222FF: \ + STACK:binmax#2222FF: \ + LINE1:bout#FF0000:out \ + LINE1:boutmin#FF2222: \ + STACK:boutmax#FF2222: \ + -v bytes/s" + +$RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP +$RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP +$RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP + +OUTPRE=lighttpd-requests + +DISP="DEF:req=$INFILE:Requests:AVERAGE \ + DEF:reqmin=$INFILE:Requests:MIN \ + DEF:reqmax=$INFILE:Requests:MAX \ + LINE1:req#0000FF:requests \ + LINE1:reqmin#2222FF: \ + STACK:reqmax#2222FF: \ + -v req/s" + +$RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP +$RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP +$RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP diff --git a/doc/rrdtool.txt b/doc/rrdtool.txt new file mode 100644 index 00000000..ac2d0e5d --- /dev/null +++ b/doc/rrdtool.txt @@ -0,0 +1,108 @@ +======= +rrdtool +======= + +------------------- +Module: mod_rrdtool +------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + mod_rrdtool is used to monitor the traffic and load on the webserver + +.. meta:: + :keywords: lighttpd, skeleton + +.. contents:: Table of Contents + +Description +=========== + +RRD_ is a system to store and display time-series data (i.e. network +bandwidth, machine-room temperature, server load average). + +.. _RRD: http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/ + +Options +======= + +rrdtool.binary + path to the rrdtool binary + + e.g.: :: + + rrdtool.binary = "/usr/bin/rrdtool" + +rrdtool.db-name + filename of the rrd-database. Make sure that <rrdtool.db-name> doesn't exists + before the first run as lighttpd has to create the DB for you. + + e.g.: :: + + rrdtool.db-name = "/var/www/lighttpd.rrd" + +Generating Graphs +================= + +:: + + #!/bin/sh + + RRDTOOL=/usr/bin/rrdtool + OUTDIR=/var/www/servers/www.example.org/pages/rrd/ + INFILE=/var/www/lighttpd.rrd + OUTPRE=lighttpd-traffic + + DISP="-v bytes --title TrafficWebserver \ + DEF:binraw=$INFILE:InOctets:AVERAGE \ + DEF:binmaxraw=$INFILE:InOctets:MAX \ + DEF:binminraw=$INFILE:InOctets:MIN \ + DEF:bout=$INFILE:OutOctets:AVERAGE \ + DEF:boutmax=$INFILE:OutOctets:MAX \ + DEF:boutmin=$INFILE:OutOctets:MIN \ + CDEF:bin=binraw,-1,* \ + CDEF:binmax=binmaxraw,-1,* \ + CDEF:binmin=binminraw,-1,* \ + AREA:binmin#ffffff: \ + STACK:binmax#f00000: \ + LINE1:binmin#a0a0a0: \ + LINE1:binmax#a0a0a0: \ + LINE2:bin#a00000:incoming \ + GPRINT:bin:MIN:%.2lf \ + GPRINT:bin:AVERAGE:%.2lf \ + GPRINT:bin:MAX:%.2lf \ + AREA:boutmin#ffffff: \ + STACK:boutmax#00f000: \ + LINE1:boutmin#a0a0a0: \ + LINE1:boutmax#a0a0a0: \ + LINE2:bout#00a000:outgoing \ + GPRINT:bout:MIN:%.2lf \ + GPRINT:bout:AVERAGE:%.2lf \ + GPRINT:bout:MAX:%.2lf \ + " + + + $RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP + $RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP + $RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP + + OUTPRE=lighttpd-requests + + DISP="-v req --title RequestsperSecond -u 1 \ + DEF:req=$INFILE:Requests:AVERAGE \ + DEF:reqmax=$INFILE:Requests:MAX \ + DEF:reqmin=$INFILE:Requests:MIN \ + AREA:reqmin#ffffff: \ + STACK:reqmax#00f000: \ + LINE1:reqmin#a0a0a0: \ + LINE1:reqmax#a0a0a0: \ + LINE2:req#006000:requests" + + + $RRDTOOL graph $OUTDIR/$OUTPRE-hour.png -a PNG --start -14400 $DISP + $RRDTOOL graph $OUTDIR/$OUTPRE-day.png -a PNG --start -86400 $DISP + $RRDTOOL graph $OUTDIR/$OUTPRE-month.png -a PNG --start -2592000 $DISP + diff --git a/doc/secdownload.txt b/doc/secdownload.txt new file mode 100644 index 00000000..5cd8f315 --- /dev/null +++ b/doc/secdownload.txt @@ -0,0 +1,142 @@ +=========================== +Secure and Fast Downloading +=========================== + +----------------------- +Module: mod_secdownload +----------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/01 07:01:29 $ +:Revision: $Revision: 1.1 $ + +:abstract: + authenticated file requests and a counter measurement against + deep-linking can be achieved easily by using mod_secdownload + +.. meta:: + :keywords: lighttpd, secure, fast, downloads + +.. contents:: Table of Contents + +Options +======= + +:: + + secdownload.secret = <string> + secdownload.document-root = <string> + secdownload.uri-prefix = <string> (default: /) + secdownload.timeout = <short> (default: 60s) + +Description +=========== + +there are multiple way to handle secured download mechanisms: + +1. use the webserver and the internal HTTP-authentification +2. use the application to authenticate and send the file + through the application + +Both way have limitations: + +webserver: + +- ``+`` fast download +- ``+`` no additional system load +- ``-`` unflexible authentification handling + +application: + +- ``+`` integrated into the overall layout +- ``+`` very flexible permission management +- ``-`` the download occupies a application thread/process + +A simple way to combine the two way could be: + +1. app authenticates user and checks permissions to + download the file. +2. app redirectes user the file accessable by the webserver + for further downloading +3. the webserver transfers the file to the user + +As the webserver doesn't know anything about the permissions +used in the app the resulting URL would be available to every +user who knows the URL. + +mod_secdownload removes this problem by introducing a way to +authenticate a URL for a specified time. The application has +to generate a token and a timestamp which are checked by the +webserver before it allows the file to be downloaded by the +webserver. + +The generated URL has to have the format: + +<uri-prefix><token>/<timestamp-in-hex><rel-path> + +<token> is a MD5 of + +1. a secret string (user supplied) +2. <rel-path> (startes with /) +3. <timestamp-in-hex> + + +As you can see the token is not bound to the user at all. The +only limiting factor is the timestamp which is used to +invalidate the URL after a given timeout (secdownload.timeout). + +.. Note:: + Be sure to choose a another secret then used in the examples + as this is the only part of the token that is not known to + the user. + + + +If the user tries to fake the URL by choosing a random token +status 403 'Forbidden' will be sent out. + +If the timeout is reached status 408 'Request Timeout' will be +sent (this not really standard conforming but should do the +trick). + +If token and timeout are valid the <rel-path> is taken and +appended at the configured (secdownload.document-root) and +passed to the normal internal file transfer functionality. +This might lead to status 200 or 404. + +Example +======= + +Application +----------- +:: + + <?php + + $secret = "verysecret"; + $uri_prefix = "/dl/"; + + # filename + $f = "/secret-file.txt"; + + # current timestamp + $t = time(); + + $t_hex = sprintf("%08x", $t); + $m = md5($secret.$f.$t_hex); + + # generate link + printf('<a href="%s%s/%s%s">%s</a>', + $uri_prefix, $m, $t_hex, $f, $f); + ?> + +Webserver +--------- +:: + + server.modules = ( ..., "mod_secdownload", ... ) + + secdownload.secret = "verysecret" + secdownload.document-root = "/home/www/servers/download-area/" + secdownload.uri-prefix = "/dl/" + diff --git a/doc/security.txt b/doc/security.txt new file mode 100644 index 00000000..d4e9147c --- /dev/null +++ b/doc/security.txt @@ -0,0 +1,60 @@ +================= +Security Features +================= + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:44:53 $ +:Revision: $Revision: 1.2 $ + +:abstract: + lighttpd was developed with security in mind ... + +.. meta:: + :keywords: lighttpd, security + +.. contents:: Table of Contents + +Description +=========== + +Limiting POST requests +---------------------- + + + +:: + + server.max-request-size = <kbyte> + +System Security +--------------- + +Running daemons as root will full privileges is a bad idea in general. +lighttpd runs best without any extra privileges and runs perfectly in chroot. + +Change Root +``````````` + +server.chroot = "..." + +Drop root-privileges +```````````````````` + +server.username = "..." +server.groupname = "..." + +FastCGI +``````` + +fastcgi + chroot + +Permissions +``````````` + +:: + + $ useradd wwwrun ... diff --git a/doc/setenv.txt b/doc/setenv.txt new file mode 100644 index 00000000..b04b0838 --- /dev/null +++ b/doc/setenv.txt @@ -0,0 +1,37 @@ +=========================== +Conditional Request Headers +=========================== + +------------------ +Module: mod_setenv +------------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + mod_setenv is used to add request + +.. meta:: + :keywords: lighttpd, skeleton + +.. contents:: Table of Contents + +Description +=========== + +... + +Options +======= + +setenv.add-environment + adds a value to the process environment that is passed to the external applications + + +setenv.add-response-header + add a header to the HTTP-response sent to the client + +setenv.add-request-header + add a header to the HTTP-request that was received by the client diff --git a/doc/simple-vhost.txt b/doc/simple-vhost.txt new file mode 100644 index 00000000..b679296e --- /dev/null +++ b/doc/simple-vhost.txt @@ -0,0 +1,73 @@ +====================== +Simple Virtual-Hosting +====================== + +------------------------ +Module: mod_simple_vhost +------------------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + virtual hosting + +.. meta:: + :keywords: lighttpd, virtual hosting + +.. contents:: Table of Contents + +Description +=========== + +Simple assumption: + +Every virtual host is in a direction below a base directory in a path that +is the same as the name of the vhost. Below this vhost-path might be a +extra directory which is the document-root of the vhost. + +The document-root for each vhost is build from three values: + +- server-root +- hostname +- document-root + +Either the absolute documentroot is build by :: + + server-root + hostname + document-root + +or if this path does not exist by :: + + server-root + default-host + document-root + +A small example should make this thinking clean: :: + + /var/www/ + /var/www/logs/ + /var/www/servers/ + /var/www/servers/www.example.org/ + /var/www/servers/www.example.org/lib/ + /var/www/servers/www.example.org/pages/ + /var/www/servers/mail.example.org/ + /var/www/servers/mail.example.org/lib/ + /var/www/servers/mail.example.org/pages/ + + simple-vhost.server-root = "/var/www/servers/" + simple-vhost.default-host = "www.example.org" + simple-vhost.document-root = "pages" + +You can use symbolic links to map several hostnames to the same directory. + +Options +======= + +simple-vhost.server-root + root of the virtual hosting + +simple-vhost.default-host + use this hostname if the + +simple-vhost.document-root + path below the vhost-directory + diff --git a/doc/skeleton.txt b/doc/skeleton.txt new file mode 100644 index 00000000..13c6881d --- /dev/null +++ b/doc/skeleton.txt @@ -0,0 +1,29 @@ +=================== +headline +=================== + +-------------------- +Module: mod_skeleton +-------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + a nice, short abstrace about the module + +.. meta:: + :keywords: lighttpd, skeleton + +.. contents:: Table of Contents + +Description +=========== + +... + +Options +======= + +... diff --git a/doc/spawn-php.sh b/doc/spawn-php.sh new file mode 100755 index 00000000..73abf671 --- /dev/null +++ b/doc/spawn-php.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +## ABSOLUTE path to the spawn-fcgi binary +SPAWNFCGI="/home/weigon/projects/spawn-fcgi/src/spawn-fcgi" + +## ABSOLUTE path to the PHP binary +FCGIPROGRAM="/usr/local/bin/php" + +## bind to tcp-port on localhost +FCGIPORT="1026" + +## number of PHP childs to spawn +PHP_FCGI_CHILDREN=10 + +## number of request server by a single php-process until is will be restarted +PHP_FCGI_MAX_REQUESTS=1000 + +## IP adresses where PHP should access server connections from +FCGI_WEB_SERVER_ADDRS="127.0.0.1,192.168.2.10" + +# allowed environment variables sperated by spaces +ALLOWED_ENV="ORACLE_HOME PATH USER" + +## if this script is run as root switch to the following user +USERID=wwwrun +GROUPID=wwwrun + + +################## no config below this line + +if test x$PHP_FCGI_CHILDREN = x; then + PHP_FCGI_CHILDREN=5 +fi + +export PHP_FCGI_MAX_REQUESTS +export FCGI_WEB_SERVER_ADDRS + +ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS" + +if test x$UID = x0; then + EX="$SPAWNFCGI -p $FCGIPORT -f $FCGIPROGRAM -u $USERID -g $GROUPID -C $PHP_FCGI_CHILDREN" +else + EX="$SPAWNFCGI -p $FCGIPORT -f $FCGIPROGRAM -C $PHP_FCGI_CHILDREN" +fi + +# copy the allowed environment variables +E= + +for i in $ALLOWED_ENV; do + E="$E $i=${!i}" +done + +# clean environment and set up a new one +env - $E $EX diff --git a/doc/ssi.txt b/doc/ssi.txt new file mode 100644 index 00000000..c5791a24 --- /dev/null +++ b/doc/ssi.txt @@ -0,0 +1,76 @@ +==================== +Server-Side Includes +==================== + +--------------- +Module: mod_ssi +--------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:44:53 $ +:Revision: $Revision: 1.2 $ + +:abstract: + The module for server-side includes provides a compat layer for + NSCA/Apache SSI. + +.. meta:: + :keywords: lighttpd, ssi, Server-Side Includes + +.. contents:: Table of Contents + +Description +=========== + +Configuration +------------- + +:: + + server.modules = ( ..., "mod_ssi", ... ) + ssi.extension = ( ".shtml" ) + +Supported Options +----------------- + +- ``<!--#echo var="..." -->`` +- ``<!--#include (file="..."\|virtual="...") -->`` +- ``<!--#flastmod (file="..."\|virtual="...") -->`` +- ``<!--#fsize (file="..."\|virtual="...") -->`` +- ``<!--#config timefmt="..." sizefmt="(bytes|abbrev)" -->`` +- ``<!--#printenv -->`` +- ``<!--#set var="..." value="..." -->`` +- ``<!--#if expr="..." -->`` +- ``<!--#elif expr="..." -->`` +- ``<!--#else -->`` +- ``<!--#endif -->`` + +Expression Handling +------------------- + +Every ''expr'' is interpreted: + +- logical: AND, OR, ! +- compare: =, <, <=, >, =>, != +- precedence: (, ) +- quoted strings: 'string with a dollar: $FOO' +- variable substitution: $REMOTE_ADDR +- unquoted strings: string + +Flow Control +------------ + +if, elif, else and endif can be used the insert content only under special +conditions. + +Unsupported Features +-------------------- + +The original SSI module from NCSA and Apache provided some more options +which are not supported by this module for various reasons: + +- exec +- nested virtual +- config.errmsg +- echo.encoding + diff --git a/doc/ssl.txt b/doc/ssl.txt new file mode 100644 index 00000000..8225822e --- /dev/null +++ b/doc/ssl.txt @@ -0,0 +1,58 @@ +=========== +Secure HTTP +=========== + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:44:53 $ +:Revision: $Revision: 1.2 $ + +:abstract: + How to setup SSL in lighttpd + +.. meta:: + :keywords: lighttpd, ssl + +.. contents:: Table of Contents + +Description +=========== + +lighttpd support SSLv2 and SSLv3 if it compiled against openssl. + +Configuration +------------- + +To enable SSL for the whole server you have to provide a valid +certificate and have to enable the SSL engine.:: + + ssl.engine = "enable" + ssl.pemfile = "/path/to/server.pem" + +As SSL and named-based virtual hosting can not work together you +have to use IP-based virtual hosting if you want to run multiple +SSL-servers with one lighttpd: :: + + SERVER["socket"] == "10.0.0.1:443" { + ssl.engine = "enable" + ssl.pemfile = "www.example.org.pem" + server.name = "www.example.org" + + server.document-root = "/www/servers/www.example.org/pages/" + } + + + + +Self-Signed Certificates +------------------------ + +A self-signed SSL cerifitcate can be generated with: :: + + $ openssl req -new -x509 \ + -keyout server.pem -out server.pem \ + -days 365 -nodes + diff --git a/doc/state.dot b/doc/state.dot new file mode 100644 index 00000000..551b2323 --- /dev/null +++ b/doc/state.dot @@ -0,0 +1,18 @@ +digraph state { + edge [color=green]; + connect -> reqstart -> read -> reqend -> handlereq -> respstart -> write -> respend -> connect; + edge [color=grey]; + reqend -> readpost -> handlereq [ label="POST" ]; + edge [ color=blue] + respend -> reqstart [ label="keep-alive" ]; + edge [ color=lightblue] + handlereq -> handlereq [ label="sub-request" ]; + edge [style=dashed, color=red]; + error -> close -> connect; + error -> connect; + handlereq -> error; + read -> error; + readpost -> error; + write -> error; + connect [shape=box]; +} diff --git a/doc/state.txt b/doc/state.txt new file mode 100644 index 00000000..d0768bc9 --- /dev/null +++ b/doc/state.txt @@ -0,0 +1,170 @@ +============================ +The State Engine of lighttpd +============================ + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/08/01 07:01:29 $ +:Revision: $Revision: 1.1 $ + +:abstract: + This is a short summary of the state-engine which is driving the lighttpd + webserver. It describes the basic concepts and the way the different parts + of the server are connected. + +.. meta:: + :keywords: lighttpd, state-engine + +.. contents:: Table of Contents + +Description +=========== + +States +------ + +The state-engine is currently made of 11 states which are walk-through on +the way each connection. Some of them are specific for a special operation +and some may never be hit at all. + +:connect: + waiting for a connection +:reqstart: + init the read-idle timer +:read: + read http-request-header from network +:reqend: + parse request +:readpost: + read http-request-content from network +:handlereq: + handle the request internally (might result in sub-requests) +:respstart: + prepare response header +:write: + write response-header + content to network +:respend: + cleanup environment, log request +:error: + reset connection (incl. close()) +:close: + close connection (handle lingering close) + +.. image:: state.png + +A simple GET request (green path) +--------------------------------- + +The connection is idling in the 'connect' state waiting for a connection. +As soon as the connection is set up we init the read-timer in 'reqstart' +and start to read data from the network. As soon as we get the +HTTP-request terminator (CRLFCRLF) we forward the header to the parser. + +The parsed request is handled by 'handlereq' and as soon as a decision out +the request is made it is sent to 'respstart' to prepare the +HTTP-response header. In the 'write' state the prepare content is sent out +to the network. When everything is sent 'respend' is entered to log the +request and cleanup the environment. After the close() call the connection +is set back to the 'connect' state again. + +Keep-Alive (blue path) +---------------------- + +The Keep-Alive handling is implemented by going from the 'respend' +directly to 'reqstart' without the close() and the accept() calls. + +POST requests (grey path) +------------------------- + +As requests might contain a request-body the state 'readpost' entered as +soon as the header is parsed and we know how much data we expect. + +Pipelining +---------- + +HTTP/1.1 supportes pipelining (sending multiple requests without waiting +for the response of the first request). This is handled transparently by +the 'read' state. + +Unexpected errors (red path) +---------------------------- + +For really hard errors we use the 'error' state which resets the +connection and can be call from every state. It is only use if there is no +other way to handle the issue (e.g. client-side close of the connection). +If possible we should use http-status 500 ('internal server error') and +log the issue in the errorlog. + +If we have to take care of some data which is coming in after we ran into +the error condition the 'close' state is used the init a half-close and +read all the delay packet from the network. + +Sub-Requests (lightblue) +------------------------ + +The FastCGI, CGI, ... intergration is done by introducing a loop in +'handlereq' to handle all aspect which are neccesary to find out what has +to be sent back to the client. + +Functions +========= + +Important functions used by the state-engine + +:state-engine: + +- ``connection_state_machine()`` + +:connect: + +- (nothing) + +:reqstart: + +- (nothing) + +:read: + +- ``connection_handle_read_state()`` +- ``connection_handle_read()`` + +:reqend: + +- ``http_request_parse()`` + +:readpost: + +- ``connection_handle_read_state()`` +- ``connection_handle_read()`` + +:handlereq: + +- ``http_response_prepare()`` + +:respstart: + +- ``connection_handle_write_prepare()`` + +:write: + +- ``connection_handle_write()`` + +:respend: + +- ``plugins_call_handle_request_done()`` +- ``plugins_call_handle_connection_close()`` +- ``connection_close()`` (if not keep-alive) +- ``connection_reset()`` + +:error: + +- ``plugins_call_handle_request_done()`` +- ``plugins_call_handle_connection_close()`` +- ``connection_reset()`` + +:close: + +- ``connection_close()`` diff --git a/doc/status.txt b/doc/status.txt new file mode 100644 index 00000000..3e0acab4 --- /dev/null +++ b/doc/status.txt @@ -0,0 +1,35 @@ +============= +Server Status +============= + +------------------ +Module: mod_status +------------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + mod_status displays server-status and server-config + +.. meta:: + :keywords: lighttpd, server status + +.. contents:: Table of Contents + +Description +=========== + +The server status module generates the ... + +Options +======= + +status.status-url + + Default: unset + +status.config-url + + Default: unset diff --git a/doc/sysconfig.lighttpd b/doc/sysconfig.lighttpd new file mode 100644 index 00000000..c8154c96 --- /dev/null +++ b/doc/sysconfig.lighttpd @@ -0,0 +1 @@ +LIGHTTPD_CONF_PATH=/etc/lighttpd/lighttpd.conf diff --git a/doc/traffic-shaping.txt b/doc/traffic-shaping.txt new file mode 100644 index 00000000..7d16638d --- /dev/null +++ b/doc/traffic-shaping.txt @@ -0,0 +1,55 @@ +=============== +Traffic Shaping +=============== + +------------ +Module: core +------------ + +:Author: Jan Kneschke +:Date: $Date: 2004/11/03 22:26:05 $ +:Revision: $Revision: 1.2 $ + +:abstract: + limiting the bandwith usage + +.. meta:: + :keywords: lighttpd, bandwidth limit, traffic shaping + +.. contents:: Table of Contents + +Description +=========== + +Starting with 1.3.8 lighttpd supports limiting the bandwith for a single connection +or config-context like virtual-host or URL. + +Options +======= + +:connection.kbytes-per-second: + limit the through-put for each single connection to the given + limit in kbyte/s + + default: 0 (no limit) + +:server.kbytes-per-second: + limit the through-put for all connections to the given limit + in kbyte/s + + if you want to specify a limit for a special virtual server + use: :: + + $HTTP["host"] == "www.example.org" { + server.kbytes-per-second = 128 + } + + which will overwrite the default for this host. + + default: 0 (no limit) + +Additional Notes +================ + +Keep in mind that a limit below 32kb/s might actually limit the traffic to 32kb/s. This +is caused by by the size of the TCP-sendbuffer. diff --git a/doc/userdir.txt b/doc/userdir.txt new file mode 100644 index 00000000..e0983fb3 --- /dev/null +++ b/doc/userdir.txt @@ -0,0 +1,54 @@ +======= +userdir +======= + +------------------- +Module: mod_userdir +------------------- + +:Author: Jan Kneschke +:Date: $Date: 2004/08/29 09:43:49 $ +:Revision: $Revision: 1.1 $ + +:abstract: + The userdir module ... + +.. meta:: + :keywords: lighttpd, userdir + +.. contents:: Table of Contents + +Description +=========== + +The userdir module provides a simple way to link user-based directories into the global namespace of the webserver. + +Requests in the form ``/~user/page.html`` are rewritten to take the file ``page.html`` from the home-directory of the user. +If userdir.path is set, the path will be appended at the home-directory. + +To control which users should be able to use this feature you can set a include- or a exclude list for username. + +Options +======= + +userdir.path + usually it should set the "public_html" to take ~/public_html/ as the document-root + + Default: empty (document-root is the home-directory) + Example: :: + + userdir.path = "public_html" + +userdir.exclude-user + list of usernames which should not be able to use this feature + + Default: empty (all users may use it) + Example: :: + + userdir.exclude-user = ( "root", "postmaster" ) + + +userdir.include-user + if set, only users from this list may use the feature + + Default: empty (all user may use it) diff --git a/lighttpd.spec.in b/lighttpd.spec.in new file mode 100644 index 00000000..aaed41a8 --- /dev/null +++ b/lighttpd.spec.in @@ -0,0 +1,84 @@ +Summary: A fast webserver with minimal memory-footprint (lighttpd) +Name: lighttpd +Version: @VERSION@ +Release: 1 +Source: http://jan.kneschke.de/projects/lighttpd/download/lighttpd-%version.tar.gz +Packager: Jan Kneschke <jan@kneschke.de> +License: BSD +Group: Networking/Daemons +URL: http://jan.kneschke.de/projects/lighttpd/ +Requires: pcre >= 3.1 zlib +BuildPrereq: libtool zlib-devel +BuildRoot: %{_tmppath}/%{name}-root + + +%description +lighttpd is intented to be a frontend for ad-servers which have to deliver +small files concurrently to many connections. + +Available rpmbuild rebuild options : +--with : ssl mysql + +%prep + +%setup -q + +%build +rm -rf %{buildroot} +%configure \ + %{?_with_mysql: --with-mysql} \ + %{?_with_ssl: --with-openssl} +make + +%install + +%makeinstall + +mkdir -p %{buildroot}%{_sysconfdir}/{init.d,sysconfig} +if test -f /etc/redhat-release -o -f /etc/fedora-release; then + install -m 755 doc/rc.lighttpd.redhat %{buildroot}%{_sysconfdir}/init.d/lighttpd +else + install -m 755 doc/rc.lighttpd %{buildroot}%{_sysconfdir}/init.d/lighttpd +fi +install -m 644 doc/sysconfig.lighttpd %{buildroot}%{_sysconfdir}/sysconfig/lighttpd + +%clean +rm -rf %{buildroot} + +%post + +if test "$1" = "0"; then + # real install, not upgrade + /sbin/chkconfig --add lighttpd +fi + +%preun +if test "$1" = "0"; then + # real uninstall, not upgrade + %{_sysconfdir}/init.d/lighttpd stop + /sbin/chkconfig --del lighttpd +fi + +%files +%defattr(-,root,root) +%doc doc/lighttpd.conf doc/lighttpd.user README INSTALL ChangeLog COPYING AUTHORS +%doc doc/*.txt +%config(noreplace) %attr(0755,root,root) %{_sysconfdir}/init.d/lighttpd +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/sysconfig/lighttpd +%{_mandir}/* +%{_libdir}/* +%{_sbindir}/* +%{_bindir}/* + +%changelog +* Thu Sep 30 2004 12:41 <jan@kneschke.de> 1.3.1 +- upgraded to 1.3.1 + +* Tue Jun 29 2004 17:26 <jan@kneschke.de> 1.2.3 +- rpmlint'ed the package +- added URL +- added (noreplace) to start-script +- change group to Networking/Daemon (like apache) + +* Sun Feb 23 2003 15:04 <jan@kneschke.de> +- initial version diff --git a/openwrt/.cvsignore b/openwrt/.cvsignore new file mode 100644 index 00000000..3dda7298 --- /dev/null +++ b/openwrt/.cvsignore @@ -0,0 +1,2 @@ +Makefile.in +Makefile diff --git a/openwrt/Makefile.am b/openwrt/Makefile.am new file mode 100644 index 00000000..6bb5b8cd --- /dev/null +++ b/openwrt/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST=conffiles control lighttpd.conf S51lighttpd lighttpd.mk diff --git a/openwrt/S51lighttpd b/openwrt/S51lighttpd new file mode 100755 index 00000000..c9aa1f9f --- /dev/null +++ b/openwrt/S51lighttpd @@ -0,0 +1,4 @@ +#!/bin/sh + +/usr/sbin/lighttpd -f /etc/lighttpd.conf +exit $? diff --git a/openwrt/conffiles b/openwrt/conffiles new file mode 100644 index 00000000..e90f8e19 --- /dev/null +++ b/openwrt/conffiles @@ -0,0 +1 @@ +/etc/lighttpd.conf diff --git a/openwrt/control b/openwrt/control new file mode 100644 index 00000000..3c085e49 --- /dev/null +++ b/openwrt/control @@ -0,0 +1,9 @@ +Package: lighttpd +Version: 1.3.11 +Architecture: mipsel +Maintainer: Jan Kneschke <jan@kneschke.de> +Source: http://jan.kneschke.de/projects/lighttpd/download/lighttpd-1.3.11.tar.gz +Section: net +Priority: optional +Depends: +Description: Flexible and Lightweight Webserver diff --git a/openwrt/control.in b/openwrt/control.in new file mode 100644 index 00000000..3a4c5715 --- /dev/null +++ b/openwrt/control.in @@ -0,0 +1,9 @@ +Package: lighttpd +Version: @VERSION@ +Architecture: mipsel +Maintainer: Jan Kneschke <jan@kneschke.de> +Source: http://jan.kneschke.de/projects/lighttpd/download/lighttpd-@VERSION@.tar.gz +Section: net +Priority: optional +Depends: +Description: Flexible and Lightweight Webserver diff --git a/openwrt/lighttpd.conf b/openwrt/lighttpd.conf new file mode 100644 index 00000000..42d2f769 --- /dev/null +++ b/openwrt/lighttpd.conf @@ -0,0 +1,231 @@ +# lighttpd configuration file +# +# use a it as base for lighttpd 1.0.0 and above +# +# $Id: lighttpd.conf,v 1.6 2004/08/29 09:44:53 weigon Exp $ + +############ Options you really have to take care of #################### + +## modules to load +# at least mod_access and mod_accesslog should be loaded +# all other module should only be loaded if really neccesary +# - saves some time +# - saves memory +server.modules = ( +# "mod_rewrite", +# "mod_redirect", + "mod_access", +# "mod_auth", +# "mod_status", +# "mod_fastcgi", +# "mod_simple_vhost", +# "mod_evhost", +# "mod_cgi", +# "mod_compress", +# "mod_ssi", +# "mod_usertrack", +# "mod_rrdtool", +# "mod_accesslog" +) + +## a static document-root, for virtual-hosting take look at the +## server.virtual-* options +server.document-root = "/www/" + +## where to send error-messages to +# server.errorlog = "" + +# files to check for if .../ is requested +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +# mimetype mapping +mimetype.assign = ( + ".pdf" => "application/pdf", + ".sig" => "application/pgp-signature", + ".spl" => "application/futuresplash", + ".class" => "application/octet-stream", + ".ps" => "application/postscript", + ".torrent" => "application/x-bittorrent", + ".dvi" => "application/x-dvi", + ".gz" => "application/x-gzip", + ".pac" => "application/x-ns-proxy-autoconfig", + ".swf" => "application/x-shockwave-flash", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".tar" => "application/x-tar", + ".zip" => "application/zip", + ".mp3" => "audio/mpeg", + ".m3u" => "audio/x-mpegurl", + ".wma" => "audio/x-ms-wma", + ".wax" => "audio/x-ms-wax", + ".ogg" => "audio/x-wav", + ".wav" => "audio/x-wav", + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".xbm" => "image/x-xbitmap", + ".xpm" => "image/x-xpixmap", + ".xwd" => "image/x-xwindowdump", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".js" => "text/javascript", + ".asc" => "text/plain", + ".c" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mov" => "video/quicktime", + ".qt" => "video/quicktime", + ".avi" => "video/x-msvideo", + ".asf" => "video/x-ms-asf", + ".asx" => "video/x-ms-asf", + ".wmv" => "video/x-ms-wmv" + ) + +# Use the "Content-Type" extended attribute to obtain mime type if possible +# mimetypes.use-xattr = "enable" + +#### accesslog module +# accesslog.filename = "/www/logs/access.log" + +## deny access the file-extensions +# +# ~ is for backupfiles from vi, emacs, joe, ... +# .inc is often used for code includes which should in general not be part +# of the document-root +url.access-deny = ( "~", ".inc" ) + + + +######### Options that are good to be but not neccesary to be changed ####### + +## bind to port (default: 80) +#server.port = 81 + +## bind to localhost (default: all interfaces) +#server.bind = "grisu.home.kneschke.de" + +## error-handler for status 404 +#server.error-handler-404 = "/error-handler.html" +#server.error-handler-404 = "/error-handler.php" + + +###### virtual hosts +## +## If you want name-based virtual hosting add the next three settings and load +## mod_simple_vhost +## +## document-root = +## virtual-server-root + virtual-server-default-host + virtual-server-docroot or +## virtual-server-root + http-host + virtual-server-docroot +## +#simple-vhost.server-root = "/home/weigon/wwwroot/servers/" +#simple-vhost.default-host = "grisu.home.kneschke.de" +#simple-vhost.document-root = "/pages/" + + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +## virtual directory listings +#server.dir-listing = "enable" + +## send unhandled HTTP-header headers to error-log +#debug.dump-unknown-headers = "enable" + +### only root can use these options +# +# chroot() to directory (default: no chroot() ) +#server.chroot = "/" + +## change uid to <uid> (default: don't care) +#server.username = "wwwrun" + +## change uid to <uid> (default: don't care) +#server.groupname = "wwwrun" + +#### compress module +#compress.cache-dir = "/tmp/lighttpd/cache/compress/" +#compress.filetype = ("text/plain", "text/html") + +#### fastcgi module +## read fastcgi.txt for more info +#fastcgi.server = ( ".php" => +# ( "grisu" => +# ( +# "host" => "192.168.2.10", +# "port" => 1026 +# ) +# ) +# ) + +#### CGI module +#cgi.assign = ( ".pl" => "/usr/bin/perl", +# ".cgi" => "/usr/bin/perl" ) +# + +#### SSL engine +#ssl.engine = "enable" +#ssl.pemfile = "server.pem" + +#### status module +# status.status-url = "/server-status" +# status.config-url = "/server-config" + +#### auth module +## read authentification.txt for more info +# auth.backend = "plain" +# auth.backend.plain.userfile = "lighttpd.user" +# auth.backend.plain.groupfile = "lighttpd.group" + +# auth.backend.ldap.hostname = "localhost" +# auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +# auth.backend.ldap.filter = "(uid=$)" + +# auth.require = ( "/server-status" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "group=www|user=jan|host=192.168.2.10" +# ), +# "/server-info" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "group=www|user=jan|host=192.168.2.10" +# ) +# ) + +#### url handling modules (rewrite, redirect, access) +# url.rewrite = ( "^/$" => "/server-status" ) +# url.redirect = ( "^/wishlist/(.+)" => "http://www.123.org/$1" ) + +# +# 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/storage/dev/www/%3/htdocs/" + +#### expire module +# expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#### ssi +# ssi.extension = ( ".shtml" ) + +#### rrdtool +# rrdtool.binary = "/usr/bin/rrdtool" +# rrdtool.db-name = "/var/www/lighttpd.rrd" diff --git a/openwrt/lighttpd.mk b/openwrt/lighttpd.mk new file mode 100644 index 00000000..e70972c1 --- /dev/null +++ b/openwrt/lighttpd.mk @@ -0,0 +1,72 @@ +###################################################### +# +# An example makefile to fetch a package from sources +# then fetch the ipkg updates required to the base package +# extract the archives into the build tree +# and then build the source +# +###################################################### + + +# For this example we'll use a fairly simple package that compiles easily +# and has sources available for download at sourceforge +LIGHTTPD=lighttpd-1.3.11 +LIGHTTPD_TARGET=.built +LIGHTTPD_DIR=$(BUILD_DIR)/$(LIGHTTPD) +LIGHTTPD_IPK=$(BUILD_DIR)/$(LIGHTTPD)_mipsel.ipk +LIGHTTPD_IPK_DIR=$(BUILD_DIR)/$(LIGHTTPD)-ipk + +LIGHTTPD_SITE=http://jan.kneschke.de/projects/lighttpd/download/ +LIGHTTPD_SOURCE=$(LIGHTTPD).tar.gz + + +# We need to download sources if we dont have them +$(DL_DIR)/$(LIGHTTPD_SOURCE) : + $(WGET) -P $(DL_DIR) $(LIGHTTPD_SITE)/$(LIGHTTPD_SOURCE) + +# if we have the sources, they do no good unless they are unpacked +$(LIGHTTPD_DIR)/.unpacked: $(DL_DIR)/$(LIGHTTPD_SOURCE) + gzip -cd $(DL_DIR)/$(LIGHTTPD_SOURCE) | tar -C $(BUILD_DIR) -xvf - + touch $(LIGHTTPD_DIR)/.unpacked + +# if we have the sources unpacked, we need to configure them +$(LIGHTTPD_DIR)/.configured: $(LIGHTTPD_DIR)/.unpacked + (cd $(LIGHTTPD_DIR); rm -rf config.cache; \ + $(TARGET_CONFIGURE_OPTS) \ + CFLAGS="$(TARGET_CFLAGS)" \ + LD=$(TARGET_CROSS)ld \ + ./configure \ + --target=$(GNU_TARGET_NAME) \ + --host=$(GNU_TARGET_NAME) \ + --build=$(GNU_HOST_NAME) \ + --prefix=/usr \ + --exec-prefix=/usr \ + --bindir=/usr/bin \ + --sbindir=/usr/sbin \ + --disable-zlib \ + --sysconfdir=/etc \ + --program-transform-name="s,y,y," \ + ); + touch $(LIGHTTPD_DIR)/.configured + + +# now that we have it all in place, just build it +$(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET): $(LIGHTTPD_DIR)/.configured + cd $(LIGHTTPD_DIR) && $(MAKE) CC=$(TARGET_CC) DESTDIR="$(LIGHTTPD_IPK_DIR)" install + $(STAGING_DIR)/bin/sstrip $(LIGHTTPD_IPK_DIR)/usr/sbin/lighttpd + touch $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET) + +$(LIGHTTPD_IPK): uclibc $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET) + mkdir -p $(LIGHTTPD_IPK_DIR)/CONTROL + mkdir -p $(LIGHTTPD_IPK_DIR)/etc/init.d + cp $(LIGHTTPD_DIR)/openwrt/conffiles $(LIGHTTPD_IPK_DIR)/CONTROL + cp $(LIGHTTPD_DIR)/openwrt/control $(LIGHTTPD_IPK_DIR)/CONTROL + cp $(LIGHTTPD_DIR)/openwrt/S51lighttpd $(LIGHTTPD_IPK_DIR)/etc/init.d/ + cp $(LIGHTTPD_DIR)/openwrt/lighttpd.conf $(LIGHTTPD_IPK_DIR)/etc/ + + rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.a + rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.la + + cd $(BUILD_DIR); $(IPKG_BUILD) $(LIGHTTPD_IPK_DIR) + +lighttpd-ipk: $(LIGHTTPD_IPK) diff --git a/openwrt/lighttpd.mk.in b/openwrt/lighttpd.mk.in new file mode 100644 index 00000000..9ee61705 --- /dev/null +++ b/openwrt/lighttpd.mk.in @@ -0,0 +1,72 @@ +###################################################### +# +# An example makefile to fetch a package from sources +# then fetch the ipkg updates required to the base package +# extract the archives into the build tree +# and then build the source +# +###################################################### + + +# For this example we'll use a fairly simple package that compiles easily +# and has sources available for download at sourceforge +LIGHTTPD=lighttpd-@VERSION@ +LIGHTTPD_TARGET=.built +LIGHTTPD_DIR=$(BUILD_DIR)/$(LIGHTTPD) +LIGHTTPD_IPK=$(BUILD_DIR)/$(LIGHTTPD)_mipsel.ipk +LIGHTTPD_IPK_DIR=$(BUILD_DIR)/$(LIGHTTPD)-ipk + +LIGHTTPD_SITE=http://jan.kneschke.de/projects/lighttpd/download/ +LIGHTTPD_SOURCE=$(LIGHTTPD).tar.gz + + +# We need to download sources if we dont have them +$(DL_DIR)/$(LIGHTTPD_SOURCE) : + $(WGET) -P $(DL_DIR) $(LIGHTTPD_SITE)/$(LIGHTTPD_SOURCE) + +# if we have the sources, they do no good unless they are unpacked +$(LIGHTTPD_DIR)/.unpacked: $(DL_DIR)/$(LIGHTTPD_SOURCE) + gzip -cd $(DL_DIR)/$(LIGHTTPD_SOURCE) | tar -C $(BUILD_DIR) -xvf - + touch $(LIGHTTPD_DIR)/.unpacked + +# if we have the sources unpacked, we need to configure them +$(LIGHTTPD_DIR)/.configured: $(LIGHTTPD_DIR)/.unpacked + (cd $(LIGHTTPD_DIR); rm -rf config.cache; \ + $(TARGET_CONFIGURE_OPTS) \ + CFLAGS="$(TARGET_CFLAGS)" \ + LD=$(TARGET_CROSS)ld \ + ./configure \ + --target=$(GNU_TARGET_NAME) \ + --host=$(GNU_TARGET_NAME) \ + --build=$(GNU_HOST_NAME) \ + --prefix=/usr \ + --exec-prefix=/usr \ + --bindir=/usr/bin \ + --sbindir=/usr/sbin \ + --disable-zlib \ + --sysconfdir=/etc \ + --program-transform-name="s,y,y," \ + ); + touch $(LIGHTTPD_DIR)/.configured + + +# now that we have it all in place, just build it +$(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET): $(LIGHTTPD_DIR)/.configured + cd $(LIGHTTPD_DIR) && $(MAKE) CC=$(TARGET_CC) DESTDIR="$(LIGHTTPD_IPK_DIR)" install + $(STAGING_DIR)/bin/sstrip $(LIGHTTPD_IPK_DIR)/usr/sbin/lighttpd + touch $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET) + +$(LIGHTTPD_IPK): uclibc $(LIGHTTPD_DIR)/$(LIGHTTPD_TARGET) + mkdir -p $(LIGHTTPD_IPK_DIR)/CONTROL + mkdir -p $(LIGHTTPD_IPK_DIR)/etc/init.d + cp $(LIGHTTPD_DIR)/openwrt/conffiles $(LIGHTTPD_IPK_DIR)/CONTROL + cp $(LIGHTTPD_DIR)/openwrt/control $(LIGHTTPD_IPK_DIR)/CONTROL + cp $(LIGHTTPD_DIR)/openwrt/S51lighttpd $(LIGHTTPD_IPK_DIR)/etc/init.d/ + cp $(LIGHTTPD_DIR)/openwrt/lighttpd.conf $(LIGHTTPD_IPK_DIR)/etc/ + + rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.a + rm -f $(LIGHTTPD_IPK_DIR)/usr/lib/*.la + + cd $(BUILD_DIR); $(IPKG_BUILD) $(LIGHTTPD_IPK_DIR) + +lighttpd-ipk: $(LIGHTTPD_IPK) diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 00000000..82d670f2 --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,11 @@ +lemon +lighttpd +Makefile.in +Makefile +spawn-fcgi +chunk +.deps +.libs +array +mod_ssi_exprparser.c +configparser.c diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..c4d275fc --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,221 @@ +noinst_PROGRAMS=array chunk lemon # simple-fcgi #graphic evalo bench ajp ssl error_test adserver gen-license +sbin_PROGRAMS=lighttpd +bin_PROGRAMS=spawn-fcgi +LEMON=$(top_builddir)/src/lemon + +lemon_SOURCES=lemon.c + +#simple_fcgi_SOURCES=simple-fcgi.c +#simple_fcgi_LDADD=-lfcgi + +if CROSS_COMPILING +configparser.c configparser.h: +mod_ssi_exprparser.c mod_ssi_exprparser.h: +else +configparser.y: lemon +mod_ssi_exprparser.y: lemon + +configparser.c configparser.h: configparser.y + rm -f configparser.h + $(LEMON) -q $(srcdir)/configparser.y $(srcdir)/lempar.c + +mod_ssi_exprparser.c mod_ssi_exprparser.h: mod_ssi_exprparser.y + rm -f mod_ssi_exprparser.h + $(LEMON) -q $(srcdir)/mod_ssi_exprparser.y $(srcdir)/lempar.c +endif + +config.c: configparser.h +mod_ssi_expr.c: mod_ssi_exprparser.h + +common_src=buffer.c log.c \ + keyvalue.c response.c request.c chunk.c \ + http_chunk.c stream.c fdevent.c connections.c \ + file_cache.c plugin.c joblist.c etag.c array.c \ + data_string.c data_count.c data_array.c \ + data_integer.c md5.c data_fastcgi.c \ + fdevent_select.c fdevent_linux_rtsig.c \ + fdevent_poll.c fdevent_linux_sysepoll.c \ + fdevent_solaris_devpoll.c fdevent_freebsd_kqueue.c \ + network_write.c network_linux_sendfile.c \ + network_freebsd_sendfile.c network_writev.c \ + network_solaris_sendfilev.c network_openssl.c \ + data_config.c bitset.c configparser.c config.c \ + inet_ntop_cache.c network.c + +src = server.c + +spawn_fcgi_SOURCES=spawn-fcgi.c + +lib_LTLIBRARIES = + +if NO_RDYNAMIC +# if the linker doesn't allow referencing symbols of the binary +# we have to put everything into a shared-lib and link it into +# everything +lib_LTLIBRARIES += liblightcomp.la +liblightcomp_la_SOURCES=$(common_src) +liblightcomp_la_CFLAGS=$(AM_CFLAGS) +liblightcomp_la_LDFLAGS = -avoid-version -no-undefined +liblightcomp_la_LIBADD = $(PCRE_LIB) $(SSL_LIB) +common_libadd = liblightcomp.la +else +src += $(common_src) +common_libadd = +endif + +lib_LTLIBRARIES += mod_mysql_vhost.la +mod_mysql_vhost_la_SOURCES = mod_mysql_vhost.c +mod_mysql_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_mysql_vhost_la_LIBADD = $(MYSQL_LIBS) $(common_libadd) + +lib_LTLIBRARIES += mod_cgi.la +mod_cgi_la_SOURCES = mod_cgi.c +mod_cgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_cgi_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_setenv.la +mod_setenv_la_SOURCES = mod_setenv.c +mod_setenv_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_setenv_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_alias.la +mod_alias_la_SOURCES = mod_alias.c +mod_alias_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_alias_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_userdir.la +mod_userdir_la_SOURCES = mod_userdir.c +mod_userdir_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_userdir_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_rrdtool.la +mod_rrdtool_la_SOURCES = mod_rrdtool.c +mod_rrdtool_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_rrdtool_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_usertrack.la +mod_usertrack_la_SOURCES = mod_usertrack.c +mod_usertrack_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_usertrack_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_proxy.la +mod_proxy_la_SOURCES = mod_proxy.c +mod_proxy_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_proxy_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_ssi.la +mod_ssi_la_SOURCES = mod_ssi_exprparser.c mod_ssi_expr.c mod_ssi.c +mod_ssi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_ssi_la_LIBADD = $(common_libadd) $(PCRE_LIB) + +lib_LTLIBRARIES += mod_secdownload.la +mod_secdownload_la_SOURCES = mod_secure_download.c +mod_secdownload_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_secdownload_la_LIBADD = $(common_libadd) + +#lib_LTLIBRARIES += mod_httptls.la +#mod_httptls_la_SOURCES = mod_httptls.c +#mod_httptls_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +#mod_httptls_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_expire.la +mod_expire_la_SOURCES = mod_expire.c +mod_expire_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_expire_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_evhost.la +mod_evhost_la_SOURCES = mod_evhost.c +mod_evhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_evhost_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_simple_vhost.la +mod_simple_vhost_la_SOURCES = mod_simple_vhost.c +mod_simple_vhost_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_simple_vhost_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_fastcgi.la +mod_fastcgi_la_SOURCES = mod_fastcgi.c +mod_fastcgi_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_fastcgi_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_access.la +mod_access_la_SOURCES = mod_access.c +mod_access_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_access_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_compress.la +mod_compress_la_SOURCES = mod_compress.c crc32.c +mod_compress_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_compress_la_LIBADD = $(Z_LIB) $(BZ_LIB) $(common_libadd) + +lib_LTLIBRARIES += mod_auth.la +mod_auth_la_SOURCES = mod_auth.c http_auth_digest.c http_auth.c +mod_auth_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_auth_la_LIBADD = $(CRYPT_LIB) $(LDAP_LIB) $(LBER_LIB) $(common_libadd) + +lib_LTLIBRARIES += mod_rewrite.la +mod_rewrite_la_SOURCES = mod_rewrite.c +mod_rewrite_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_rewrite_la_LIBADD = $(PCRE_LIB) $(common_libadd) + +lib_LTLIBRARIES += mod_redirect.la +mod_redirect_la_SOURCES = mod_redirect.c +mod_redirect_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_redirect_la_LIBADD = $(PCRE_LIB) $(common_libadd) + +lib_LTLIBRARIES += mod_status.la +mod_status_la_SOURCES = mod_status.c +mod_status_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_status_la_LIBADD = $(common_libadd) + +lib_LTLIBRARIES += mod_accesslog.la +mod_accesslog_la_SOURCES = mod_accesslog.c +mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined +mod_accesslog_la_LIBADD = $(common_libadd) + + +hdr = server.h buffer.h network.h log.h keyvalue.h \ + response.h request.h fastcgi.h chunk.h \ + settings.h http_chunk.h http_auth_digest.h \ + md5.h md5_global.h http_auth.h stream.h \ + fdevent.h connections.h base.h file_cache.h \ + plugin.h mod_auth.h \ + etag.h joblist.h array.h crc32.h \ + network_backends.h configfile.h bitset.h \ + mod_ssi.h mod_ssi_expr.h inet_ntop_cache.h \ + configparser.h mod_ssi_exprparser.h \ + sys-mmap.h sys-socket.h + # license.h + +#hdr += chat.h chat_misc.h chat_endec.h chat_user.h \ +# chat_channel.h + +DEFS= @DEFS@ -DLIBRARY_DIR="\"$(libdir)\"" + +lighttpd_SOURCES = $(src) +lighttpd_LDADD = $(PCRE_LIB) $(DL_LIB) $(SENDFILE_LIB) $(ATTR_LIB) $(common_libadd) $(SSL_LIB) +lighttpd_LDFLAGS = -export-dynamic +lighttpd_CCPFLAGS = $(MYSQL_INCLUDES) + +array_SOURCES = array.c buffer.c data_string.c data_count.c +array_CPPFLAGS= -DDEBUG_ARRAY + +chunk_SOURCES = buffer.c chunk.c +chunk_CPPFLAGS= -DDEBUG_CHUNK + +#gen_license_SOURCES = license.c md5.c buffer.c gen_license.c + +#ssl_SOURCES = ssl.c + + +#adserver_SOURCES = buffer.c iframe.c +#adserver_LDADD = -lfcgi -lmysqlclient + +#error_test_SOURCES = error_test.c + +#evalo_SOURCES = buffer.c eval.c +#bench_SOURCES = buffer.c bench.c +#ajp_SOURCES = ajp.c + +noinst_HEADERS = $(hdr) +EXTRA_DIST = mod_skeleton.c configparser.y mod_ssi_exprparser.y lempar.c diff --git a/src/array.c b/src/array.c new file mode 100644 index 00000000..a57abb36 --- /dev/null +++ b/src/array.c @@ -0,0 +1,241 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include <errno.h> +#include <assert.h> + +#include "array.h" +#include "buffer.h" + +array *array_init(void) { + array *a; + + a = calloc(1, sizeof(*a)); + assert(a); + + a->next_power_of_2 = 1; + + return a; +} + +void array_free(array *a) { + size_t i; + if (!a) return; + + for (i = 0; i < a->size; i++) { + if (a->data[i]) a->data[i]->free(a->data[i]); + } + + if (a->data) free(a->data); + if (a->sorted) free(a->sorted); + + free(a); +} + +void array_reset(array *a) { + size_t i; + if (!a) return; + + for (i = 0; i < a->used; i++) { + a->data[i]->reset(a->data[i]); + } + + a->used = 0; +} + +static int array_get_index(array *a, const char *key, size_t keylen, int *rndx) { + int ndx = -1; + int i, pos = 0; + + if (key == NULL) return -1; + + /* try to find the string */ + for (i = pos = a->next_power_of_2 / 2; ; i >>= 1) { + int cmp; + + if (pos < 0) { + pos += i; + } else if (pos >= (int)a->used) { + pos -= i; + } else { + cmp = buffer_caseless_compare(key, keylen, a->data[a->sorted[pos]]->key->ptr, a->data[a->sorted[pos]]->key->used); + + if (cmp == 0) { + /* found */ + ndx = a->sorted[pos]; + break; + } else if (cmp < 0) { + pos -= i; + } else { + pos += i; + } + } + if (i == 0) break; + } + + if (rndx) *rndx = pos; + + return ndx; +} + +data_unset *array_get_element(array *a, const char *key) { + int ndx; + + if (-1 != (ndx = array_get_index(a, key, strlen(key) + 1, NULL))) { + /* found, leave here */ + + return a->data[ndx]; + } + + return NULL; +} + +data_unset *array_get_unused_element(array *a, data_type_t t) { + data_unset *ds = NULL; + + UNUSED(t); + + if (a->size == 0) return NULL; + + if (a->used == a->size) return NULL; + + if (a->data[a->used]) { + ds = a->data[a->used]; + + a->data[a->used] = NULL; + } + + return ds; +} + +int array_insert_unique(array *a, data_unset *str) { + int ndx = -1; + int pos = 0; + size_t j; + + /* generate unique index if neccesary */ + if (str->key->used == 0) { + buffer_copy_long(str->key, a->unique_ndx++); + } + + /* try to find the string */ + + if (-1 != (ndx = array_get_index(a, str->key->ptr, str->key->used, &pos))) { + /* found, leave here */ + if (a->data[ndx]->type == str->type) { + str->insert_dup(a->data[ndx], str); + } else { + fprintf(stderr, "a\n"); + } + return 0; + } + + /* insert */ + + if (a->used+1 > INT_MAX) { + /* we can't handle more then INT_MAX entries: see array_get_index() */ + return -1; + } + + if (a->size == 0) { + a->size = 16; + a->data = malloc(sizeof(*a->data) * a->size); + a->sorted = malloc(sizeof(*a->sorted) * a->size); + assert(a->data); + assert(a->sorted); + for (j = a->used; j < a->size; j++) a->data[j] = NULL; + } else if (a->size == a->used) { + a->size += 16; + a->data = realloc(a->data, sizeof(*a->data) * a->size); + a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size); + assert(a->data); + assert(a->sorted); + for (j = a->used; j < a->size; j++) a->data[j] = NULL; + } + + ndx = (int) a->used; + + a->data[a->used++] = str; + + if (pos != ndx && + ((pos < 0) || + buffer_caseless_compare(str->key->ptr, str->key->used, a->data[a->sorted[pos]]->key->ptr, a->data[a->sorted[pos]]->key->used) > 0)) { + pos++; + } + + /* move everything on step to the right */ + if (pos != ndx) { + memmove(a->sorted + (pos + 1), a->sorted + (pos), (ndx - pos) * sizeof(*a->sorted)); + } + + /* insert */ + a->sorted[pos] = ndx; + + if (a->next_power_of_2 == (size_t)ndx) a->next_power_of_2 <<= 1; + + return 0; +} + +int array_print(array *a) { + size_t i; + + for (i = 0; i < a->used; i++) { + fprintf(stderr, "%d: ", i); + a->data[i]->print(a->data[i]); + fprintf(stderr, "\n"); + } + + return 0; +} + +#ifdef DEBUG_ARRAY +int main (int argc, char **argv) { + array *a; + data_string *ds; + data_count *dc; + + UNUSED(argc); + UNUSED(argv); + + a = array_init(); + + ds = data_string_init(); + buffer_copy_string(ds->key, "abc"); + buffer_copy_string(ds->value, "alfrag"); + + array_insert_unique(a, (data_unset *)ds); + + ds = data_string_init(); + buffer_copy_string(ds->key, "abc"); + buffer_copy_string(ds->value, "hameplman"); + + array_insert_unique(a, (data_unset *)ds); + + ds = data_string_init(); + buffer_copy_string(ds->key, "123"); + buffer_copy_string(ds->value, "alfrag"); + + array_insert_unique(a, (data_unset *)ds); + + dc = data_count_init(); + buffer_copy_string(dc->key, "def"); + + array_insert_unique(a, (data_unset *)dc); + + dc = data_count_init(); + buffer_copy_string(dc->key, "def"); + + array_insert_unique(a, (data_unset *)dc); + + array_print(a); + + array_free(a); + + fprintf(stderr, "%d\n", + buffer_caseless_compare(CONST_STR_LEN("Content-Type"), CONST_STR_LEN("Content-type"))); + + return 0; +} +#endif diff --git a/src/array.h b/src/array.h new file mode 100644 index 00000000..8ac2bdcc --- /dev/null +++ b/src/array.h @@ -0,0 +1,120 @@ +#ifndef ARRAY_H +#define ARRAY_H + +#include <stdlib.h> +#include "config.h" +#ifdef HAVE_PCRE_H +# include <pcre.h> +#endif +#include "buffer.h" + +#define DATA_IS_STRING(x) (x->type == TYPE_STRING) + +typedef enum { TYPE_UNSET, TYPE_STRING, TYPE_COUNT, TYPE_ARRAY, TYPE_INTEGER, TYPE_FASTCGI, TYPE_CONFIG } data_type_t; +#define DATA_UNSET \ + data_type_t type; \ + buffer *key; \ + void (* free)(struct data_unset *p); \ + void (* reset)(struct data_unset *p); \ + int (*insert_dup)(struct data_unset *dst, struct data_unset *src); \ + void (*print)(struct data_unset *p) + +typedef struct data_unset { + DATA_UNSET; +} data_unset; + +typedef struct { + data_unset **data; + + size_t *sorted; + + size_t used; + size_t size; + + size_t unique_ndx; + + size_t next_power_of_2; +} array; + +typedef struct { + DATA_UNSET; + + int count; +} data_count; + +data_count *data_count_init(void); + +typedef struct { + DATA_UNSET; + + buffer *value; +} data_string; + +data_string *data_string_init(void); +data_string *data_response_init(void); + +typedef struct { + DATA_UNSET; + + array *value; +} data_array; + +data_array *data_array_init(void); + +typedef enum { CONFIG_COND_UNSET, CONFIG_COND_EQ, CONFIG_COND_MATCH, CONFIG_COND_NE, CONFIG_COND_NOMATCH } config_cond_t; + +/* $HTTP["host"] == "incremental.home.kneschke.de" { ... } + * comp_key cond string/regex + */ + +typedef struct { + DATA_UNSET; + + array *value; + + buffer *comp_key; + + config_cond_t cond; + + union { + buffer *string; +#ifdef HAVE_PCRE_H + pcre *regex; +#endif + } match; +} data_config; + +data_config *data_config_init(void); + +typedef struct { + DATA_UNSET; + + int value; +} data_integer; + +data_integer *data_integer_init(void); + +typedef struct { + DATA_UNSET; + + buffer *host; + + unsigned short port; + int usage; + + time_t disable_ts; + +} data_fastcgi; + +data_fastcgi *data_fastcgi_init(void); + +array *array_init(void); +void array_free(array *a); +void array_reset(array *a); +int array_insert_unique(array *a, data_unset *str); +int array_print(array *a); +data_unset *array_get_unused_element(array *a, data_type_t t); +data_unset *array_get_element(array *a, const char *key); +int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len); + +#endif diff --git a/src/base.h b/src/base.h new file mode 100644 index 00000000..a9b483d7 --- /dev/null +++ b/src/base.h @@ -0,0 +1,522 @@ +#ifndef _BASE_H_ +#define _BASE_H_ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include "config.h" + +#include <limits.h> +#ifdef HAVE_STDINT_H +# include <stdint.h> +#endif +#ifdef HAVE_INTTYPES_H +# include <inttypes.h> +#endif + +#include "buffer.h" +#include "array.h" +#include "chunk.h" +#include "keyvalue.h" +#include "settings.h" +#include "fdevent.h" +#include "sys-socket.h" + + +#if defined HAVE_LIBSSL && defined HAVE_OPENSSL_SSL_H +# define USE_OPENSSL +# include <openssl/ssl.h> +#endif + +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif + +#ifndef SIZE_MAX +# ifdef SIZE_T_MAX +# define SIZE_MAX SIZE_T_MAX +# else +# define SIZE_MAX ((size_t)~0) +# endif +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX ((size_t)~0 >> 1) +#endif + +#ifdef __APPLE__ +#include <crt_externs.h> +#define environ (* _NSGetEnviron()) +#else +extern char **environ; +#endif + +/* for solaris 2.5 and NetBSD 1.3.x */ +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +/* solaris and NetBSD 1.3.x again */ +#if (!defined(HAVE_STDINT_H)) && (!defined(HAVE_INTTYPES_H)) && (!defined(uint32_t)) +# define uint32_t u_int32_t +#endif + + +#ifndef SHUT_WR +# define SHUT_WR 1 +#endif + +#include "settings.h" + +typedef enum { T_CONFIG_UNSET, + T_CONFIG_STRING, + T_CONFIG_SHORT, + T_CONFIG_BOOLEAN, + T_CONFIG_ARRAY, + T_CONFIG_LOCAL, + T_CONFIG_DEPRECATED +} config_values_type_t; + +typedef enum { T_CONFIG_SCOPE_UNSET, + T_CONFIG_SCOPE_SERVER, + T_CONFIG_SCOPE_CONNECTION +} config_scope_type_t; + +typedef struct { + const char *key; + void *destination; + + config_values_type_t type; + config_scope_type_t scope; +} config_values_t; + +typedef enum { DIRECT, EXTERNAL } connection_type; + +typedef struct { + char *key; + connection_type type; + char *value; +} request_handler; + +typedef struct { + char *key; + char *host; + unsigned short port; + int used; + short factor; +} fcgi_connections; + + +typedef union { +#ifdef HAVE_IPV6 + struct sockaddr_in6 ipv6; +#endif + struct sockaddr_in ipv4; + struct sockaddr plain; +} sock_addr; + +/* fcgi_response_header contains ... */ +#define HTTP_STATUS BV(0) +#define HTTP_CONNECTION BV(1) +#define HTTP_CONTENT_LENGTH BV(2) +#define HTTP_DATE BV(3) +#define HTTP_LOCATION BV(4) + +typedef struct { + /** HEADER */ + /* the request-line */ + buffer *request; + buffer *uri; + + buffer *orig_uri; + + http_method_t http_method; + http_version_t http_version; + + buffer *request_line; + + /* strings to the header */ + buffer *http_host; /* not alloced */ + const char *http_range; + const char *http_content_type; + const char *http_if_modified_since; + const char *http_if_none_match; + + array *headers; + + /* CONTENT */ + buffer *content; + size_t content_length; /* returned by strtoul() */ + + /* internal representation */ + int accept_encoding; + + /* internal */ + buffer *pathinfo; +} request; + +typedef struct { + off_t content_length; + int keep_alive; /* used by the subrequests in proxy, cgi and fcgi to say the subrequest was keep-alive or not */ + + array *headers; + + enum { + HTTP_TRANSFER_ENCODING_IDENTITY, HTTP_TRANSFER_ENCODING_CHUNKED + } transfer_encoding; +} response; + +typedef struct { + buffer *name; + buffer *etag; + + struct stat st; + + int fd; + int fde_ndx; + + char *mmap_p; + size_t mmap_length; + off_t mmap_offset; + + size_t in_use; + size_t is_dirty; + + time_t stat_ts; + buffer *content_type; +} file_cache_entry; + +typedef struct { + buffer *scheme; + buffer *authority; + buffer *path; + buffer *path_raw; + buffer *query; +} request_uri; + +typedef struct { + buffer *path; + + buffer *doc_root; /* path = doc_root + rel_path */ + buffer *rel_path; + + buffer *etag; +} physical; + +typedef struct { + file_cache_entry **ptr; + + size_t size; + size_t used; + + buffer *dir_name; +} file_cache; + +typedef struct { + array *indexfiles; + array *mimetypes; + + /* virtual-servers */ + buffer *document_root; + buffer *server_name; + buffer *error_handler; + buffer *server_tag; + + unsigned short dir_listing; + unsigned short max_keep_alive_requests; + unsigned short max_keep_alive_idle; + unsigned short max_read_idle; + unsigned short max_write_idle; + unsigned short use_xattr; + unsigned short follow_symlink; + + /* debug */ + + unsigned short log_file_not_found; + unsigned short log_request_header; + unsigned short log_request_handling; + unsigned short log_response_header; + + + /* server wide */ + buffer *ssl_pemfile; + unsigned short use_ipv6; + unsigned short is_ssl; + unsigned short allow_http11; + unsigned short max_request_size; + + unsigned short kbytes_per_second; /* connection kb/s limit */ + + /* configside */ + unsigned short global_kbytes_per_second; /* */ + + off_t global_bytes_per_second_cnt; + /* server-wide traffic-shaper + * + * each context has the counter which is inited once + * a second by the global_kbytes_per_second config-var + * + * as soon as global_kbytes_per_second gets below 0 + * the connected conns are "offline" a little bit + * + * the problem: + * we somehow have to loose our "we are writable" signal + * on the way. + * + */ + off_t *global_bytes_per_second_cnt_ptr; /* */ + +#ifdef USE_OPENSSL + SSL_CTX *ssl_ctx; +#endif +} specific_config; + +typedef enum { CON_STATE_CONNECT, CON_STATE_REQUEST_START, CON_STATE_READ, CON_STATE_REQUEST_END, CON_STATE_READ_POST, CON_STATE_HANDLE_REQUEST, CON_STATE_RESPONSE_START, CON_STATE_WRITE, CON_STATE_RESPONSE_END, CON_STATE_ERROR, CON_STATE_CLOSE } connection_state_t; + +typedef struct { + connection_state_t state; + + /* timestamps */ + time_t read_idle_ts; + time_t close_timeout_ts; + time_t write_request_ts; + + time_t connection_start; + time_t request_start; + + struct timeval start_tv; + + size_t request_count; /* number of requests handled in this connection */ + + int fd; /* the FD for this connection */ + int fde_ndx; /* index for the fdevent-handler */ + int ndx; /* reverse mapping to server->connection[ndx] */ + + /* fd states */ + int is_readable; + int is_writable; + + int keep_alive; /* only request.c can enable it, all other just disable */ + + int file_started; + int file_finished; + + chunkqueue *write_queue; + chunkqueue *read_queue; + + int traffic_limit_reached; + + off_t bytes_written; /* used by mod_accesslog, mod_rrd */ + off_t bytes_written_cur_second; /* used by mod_accesslog, mod_rrd */ + off_t bytes_read; /* used by mod_accesslog, mod_rrd */ + off_t bytes_header; + + int http_status; + + sock_addr dst_addr; + + /* request */ + buffer *parse_request; + unsigned int parsed_response; /* bitfield which contains the important header-fields of the parsed response header */ + + request request; + request_uri uri; + physical physical; + response response; + + size_t header_len; + + buffer *authed_user; + array *environment; /* used to pass lighttpd internal stuff to the FastCGI/CGI apps, setenv does that */ + + /* response */ + int got_response; + + int in_joblist; + + connection_type mode; + + file_cache_entry *fce; /* filecache entry for the selected file */ + + void **plugin_ctx; /* plugin connection specific config */ + + specific_config conf; /* global connection specific config */ + + buffer *server_name; + + /* error-handler */ + buffer *error_handler; + int error_handler_saved_status; + int in_error_handler; + + void *srv_socket; /* reference to the server-socket (typecast to server_socket) */ + +#ifdef USE_OPENSSL + SSL *ssl; +#endif +} connection; + +typedef struct { + connection **ptr; + size_t size; + size_t used; +} connections; + + +#ifdef HAVE_IPV6 +typedef struct { + int family; + union { + struct in6_addr ipv6; + struct in_addr ipv4; + } addr; + char b2[INET6_ADDRSTRLEN + 1]; + time_t ts; +} inet_ntop_cache_type; +#endif + + +typedef struct { + buffer *uri; + time_t mtime; + int http_status; +} realpath_cache_type; + +typedef struct { + time_t mtime; /* the key */ + buffer *str; /* a buffer for the string represenation */ +} mtime_cache_type; + +typedef struct { + void *ptr; + size_t used; + size_t size; +} buffer_plugin; + +typedef struct { + unsigned short port; + buffer *bindhost; + buffer *error_logfile; + unsigned short dont_daemonize; + buffer *changeroot; + buffer *username; + buffer *groupname; + + buffer *errorfile_prefix; + buffer *license; + buffer *pid_file; + + buffer *event_handler; + + array *modules; + + unsigned short max_worker; + unsigned short max_fds; + + unsigned short log_request_header_on_error; + unsigned short log_state_handling; +} server_config; + +typedef struct { + sock_addr addr; + int fd; + int fde_ndx; + + buffer *ssl_pemfile; + unsigned short use_ipv6; + unsigned short is_ssl; + unsigned short max_request_size; + + buffer *srv_token; + +#ifdef USE_OPENSSL + SSL_CTX *ssl_ctx; +#endif +} server_socket; + +typedef struct { + server_socket **ptr; + + size_t size; + size_t used; +} server_socket_array; + +typedef struct { + server_socket_array srv_sockets; + + int log_error_fd; + int log_using_syslog; + fdevents *ev, *ev_ins; + + buffer_plugin plugins; + void *plugin_slots; + + int con_opened; + int con_read; + int con_written; + int con_closed; + + int ssl_is_init; + + int max_fds; /* max possible fds */ + int cur_fds; /* currently used fds */ + int want_fds; /* waiting fds */ + int sockets_disabled; + + /* buffers */ + buffer *parse_full_path; + buffer *response_header; + buffer *error_log; + buffer *response_range; + buffer *tmp_buf; + + buffer *tmp_chunk_len; + + buffer *range_buf; + + buffer *empty_string; /* is necessary for cond_match */ + + /* caches */ +#ifdef HAVE_IPV6 + inet_ntop_cache_type inet_ntop_cache[INET_NTOP_CACHE_MAX]; +#endif + mtime_cache_type mtime_cache[FILE_CACHE_MAX]; + + array *split_vals; + + /* Timestamps */ + time_t cur_ts; + time_t last_generated_date_ts; + time_t last_generated_debug_ts; + time_t startup_ts; + + buffer *ts_debug_str; + buffer *ts_date_str; + + /* config-file */ + array *config; + array *config_touched; + + array *config_context; + specific_config **config_storage; + + server_config srvconf; + + int config_deprecated; + + connections *conns; + connections *joblist; + connections *fdwaitqueue; + + file_cache *file_cache; + buffer *file_cache_etag; + dot_stack dot_stack; + + buffer_array *config_patches; + + fdevent_handler_t event_handler; +} server; + + +#endif diff --git a/src/bitset.c b/src/bitset.c new file mode 100644 index 00000000..e40bea04 --- /dev/null +++ b/src/bitset.c @@ -0,0 +1,66 @@ +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "bitset.h" +#include "buffer.h" + +#define BITSET_BITS \ + ( CHAR_BIT * sizeof(size_t) ) + +#define BITSET_MASK(pos) \ + ( ((size_t)1) << ((pos) % BITSET_BITS) ) + +#define BITSET_WORD(set, pos) \ + ( (set)->bits[(pos) / BITSET_BITS] ) + +#define BITSET_USED(nbits) \ + ( ((nbits) + (BITSET_BITS - 1)) / BITSET_BITS ) + +bitset *bitset_init(size_t nbits) { + bitset *set; + + set = malloc(sizeof(*set)); + assert(set); + + set->bits = calloc(BITSET_USED(nbits), sizeof(*set->bits)); + set->nbits = nbits; + + assert(set->bits); + + return set; +} + +void bitset_reset(bitset *set) { + memset(set->bits, 0, BITSET_USED(set->nbits) * sizeof(*set->bits)); +} + +void bitset_free(bitset *set) { + free(set->bits); + free(set); +} + +void bitset_clear_bit(bitset *set, size_t pos) { + if (pos >= set->nbits) { + SEGFAULT(); + } + + BITSET_WORD(set, pos) &= ~BITSET_MASK(pos); +} + +void bitset_set_bit(bitset *set, size_t pos) { + if (pos >= set->nbits) { + SEGFAULT(); + } + + BITSET_WORD(set, pos) |= BITSET_MASK(pos); +} + +int bitset_test_bit(bitset *set, size_t pos) { + if (pos >= set->nbits) { + SEGFAULT(); + } + + return (BITSET_WORD(set, pos) & BITSET_MASK(pos)) != 0; +} diff --git a/src/bitset.h b/src/bitset.h new file mode 100644 index 00000000..467e13fd --- /dev/null +++ b/src/bitset.h @@ -0,0 +1,19 @@ +#ifndef _BITSET_H_ +#define _BITSET_H_ + +#include <stddef.h> + +typedef struct { + size_t *bits; + size_t nbits; +} bitset; + +bitset *bitset_init(size_t nbits); +void bitset_reset(bitset *set); +void bitset_free(bitset *set); + +void bitset_clear_bit(bitset *set, size_t pos); +void bitset_set_bit(bitset *set, size_t pos); +int bitset_test_bit(bitset *set, size_t pos); + +#endif diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 00000000..35f4c33d --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,1106 @@ +#include <stdlib.h> +#include <string.h> + +#include <stdio.h> +#include <assert.h> +#include <ctype.h> + +#include "buffer.h" + + +static const char hex_chars[] = "0123456789abcdef"; + + +/** + * init the buffer + * + */ + +buffer* buffer_init(void) { + buffer *b; + + b = malloc(sizeof(*b)); + assert(b); + + b->ptr = NULL; + b->size = 0; + b->used = 0; + + return b; +} + +/** + * free the buffer + * + */ + +void buffer_free(buffer *b) { + if (!b) return; + + if (b->size) { + free(b->ptr); + b->size = 0; + b->used = 0; + } + free(b); +} + +void buffer_reset(buffer *b) { + if (!b) return; + + /* limit don't reuse buffer larger than ... bytes */ + if (b->size > BUFFER_MAX_REUSE_SIZE) { + free(b->ptr); + b->ptr = NULL; + b->size = 0; + } + + b->used = 0; +} + + +/** + * + * allocate (if neccessary) enough space for 'size' bytes and + * set the 'used' coutner to 0 + * + */ + +#define BUFFER_PIECE_SIZE 64 + +int buffer_prepare_copy(buffer *b, size_t size) { + if (!b) return -1; + + if ((0 == b->size) || + (size > b->size)) { + if (b->size) free(b->ptr); + + b->size = size; + + /* always allocate a multiply of BUFFER_PIECE_SIZE */ + b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE); + + b->ptr = malloc(b->size); + assert(b->ptr); + } + b->used = 0; + return 0; +} + +/** + * + * increase the internal buffer (if neccessary) to append another 'size' byte + * ->used isn't changed + * + */ + +int buffer_prepare_append(buffer *b, size_t size) { + if (!b) return -1; + + if (0 == b->size) { + b->size = size; + + /* always allocate a multiply of BUFFER_PIECE_SIZE */ + b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE); + + b->ptr = malloc(b->size); + b->used = 0; + assert(b->ptr); + } else if (b->used + size > b->size) { + b->size += size; + + /* always allocate a multiply of BUFFER_PIECE_SIZE */ + b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE); + + b->ptr = realloc(b->ptr, b->size); + assert(b->ptr); + } + return 0; +} + +int buffer_copy_string(buffer *b, const char *s) { + size_t s_len; + + if (!s || !b) return -1; + + s_len = strlen(s); + if (buffer_prepare_copy(b, s_len + 1)) return -1; + + memcpy(b->ptr, s, s_len + 1); + b->used = s_len + 1; + + return 0; +} + +int buffer_copy_string_len(buffer *b, const char *s, size_t s_len) { + if (!s || !b) return -1; +#if 0 + /* removed optimization as we have to keep the empty string + * in some cases for the config handling + * + * url.access-deny = ( "" ) + */ + if (s_len == 0) return 0; +#endif + if (buffer_prepare_copy(b, s_len + 1)) return -1; + + memcpy(b->ptr, s, s_len); + b->ptr[s_len] = '\0'; + b->used = s_len + 1; + + return 0; +} + +int buffer_copy_string_buffer(buffer *b, const buffer *src) { + if (!src) return 0; + + if (src->used == 0) { + b->used = 0; + return 0; + } + return buffer_copy_string_len(b, src->ptr, src->used - 1); +} + +int buffer_append_string(buffer *b, const char *s) { + size_t s_len; + + if (!s || !b) return -1; + + /* the buffer is empty, fallback to copy */ + if (b->used == 0) { + return buffer_copy_string(b, s); + } + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + s_len = strlen(s); + if (buffer_prepare_append(b, s_len)) return -1; + + memcpy(b->ptr + b->used - 1, s, s_len + 1); + b->used += s_len; + + return 0; +} + +int buffer_append_string_rfill(buffer *b, const char *s, size_t maxlen) { + size_t s_len; + size_t m; + ssize_t fill_len; + + if (!s || !b) return -1; + + /* the buffer is empty, fallback to copy */ + if (b->used == 0) { + return buffer_copy_string(b, s); + } + + if (b->ptr[b->used - 1] != '\0') { + /* seg-fault */ + SEGFAULT(); + } + + s_len = strlen(s); + + m = s_len > maxlen + 1 ? s_len : maxlen + 1; + + if (buffer_prepare_append(b, m)) return -1; + + fill_len = maxlen - s_len; + + if (fill_len > 0) { + memcpy(b->ptr + b->used - 1, s, s_len); + memset(b->ptr + b->used + s_len - 1, ' ', fill_len); + *(b->ptr + b->used + s_len + fill_len - 1) = '\0'; + b->used += s_len + fill_len; + } else { + memcpy(b->ptr + b->used - 1, s, s_len + 1); + b->used += s_len; + } + + return 0; +} + +/** + * append a string to the end of the buffer + * + * the resulting buffer is terminated with a '\0' + * s is treated as a un-terminated string (a \0 is handled a normal character) + * + * @param b a buffer + * @param s the string + * @param s_len size of the string (without the terminating \0) + */ + +int buffer_append_string_len(buffer *b, const char *s, size_t s_len) { + if (!s || !b) return -1; + + if (s_len == 0) return 0; + + /* the buffer is empty, fallback to copy */ + if (b->used == 0) { + return buffer_copy_string_len(b, s, s_len); + } + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + if (buffer_prepare_append(b, s_len)) return -1; + + memcpy(b->ptr + b->used - 1, s, s_len); + b->ptr[b->used + s_len - 1] = '\0'; + b->used += s_len; + + return 0; +} + +int buffer_append_string_buffer(buffer *b, const buffer *src) { + if (!src) return 0; + if (src->used == 0) return 0; + + return buffer_append_string_len(b, src->ptr, src->used - 1); +} + +int buffer_append_memory(buffer *b, const char *s, size_t s_len) { + if (!s || !b) return -1; + + if (s_len == 0) return 0; + + if (buffer_prepare_append(b, s_len)) return -1; + + memcpy(b->ptr + b->used, s, s_len); + b->used += s_len; + + return 0; +} + +int buffer_copy_memory(buffer *b, const char *s, size_t s_len) { + if (!s || !b) return -1; + + b->used = 0; + + return buffer_append_memory(b, s, s_len); +} + +int buffer_append_hex(buffer *b, unsigned long value) { + char *buf; + int shift = 0; + unsigned long copy = value; + + while (copy) { + copy >>= 4; + shift++; + } + if (shift == 0) + shift++; + if (shift & 0x01) + shift++; + + buffer_prepare_append(b, shift + 1); + buf = b->ptr + b->used; + b->used += shift + 1; + + shift <<= 2; + while (shift > 0) { + shift -= 4; + *(buf++) = hex_chars[(value >> shift) & 0x0F]; + } + *buf = '\0'; + + return 0; +} + + +int ltostr(char *s, long l) { + int i, sign = 0; + + if (l < 0) { + sign = 1; + l = -l; + } + + for (i = 0; l > 9; l /= 10, i++) { + s[i] = '0' + (l % 10); + } + + s[i] = '0' + l; + if (sign) { + s[++i] = '-'; + } + s[i+1] = '\0'; + + /* swap bytes again :) */ + if (i > 0) { + int li = i; + for (; i > li / 2; i--) { + char c; + + c = s[i]; + s[i] = s[li - i]; + s[li - i] = c; + } + } + + return 0; +} + +int buffer_copy_long(buffer *b, long l) { + int i, sign = 0; + char *s; + + if (!b) return -1; + + b->used = 0; + + if (buffer_prepare_append(b, 32)) return -1; + + s = b->ptr + b->used; + + if (l < 0) { + sign = 1; + l = -l; + } + + for (i = 0; l > 9; l /= 10, i++) { + s[i] = '0' + (l % 10); + } + + s[i] = '0' + l; + if (sign) { + s[++i] = '-'; + } + s[i+1] = '\0'; + b->used = i + 2; + + /* swap bytes again :) */ + if (i > 0) { + int li = i; + for (; i > li / 2; i--) { + char c; + + c = s[i]; + s[i] = s[li - i]; + s[li - i] = c; + } + } + + return 0; +} + +int buffer_append_long(buffer *b, long l) { + int i, sign = 0; + char *s; + + if (!b) return -1; + + /* the buffer is empty, fallback to copy */ + if (b->used == 0) { + SEGFAULT(); + } + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + if (buffer_prepare_append(b, 32)) return -1; + + s = b->ptr + b->used - 1; + + if (l < 0) { + sign = 1; + l = -l; + } + + for (i = 0; l > 9; l /= 10, i++) { + s[i] = '0' + (l % 10); + } + + s[i] = '0' + l; + if (sign) { + s[++i] = '-'; + } + s[i+1] = '\0'; + b->used += i + 1; + + /* swap bytes again :) */ + if (i > 0) { + int li = i; + for (; i > li / 2; i--) { + char c; + + c = s[i]; + s[i] = s[li - i]; + s[li - i] = c; + } + } + + return 0; +} + + +int buffer_copy_off_t(buffer *b, off_t l) { + int i, sign = 0; + char *s; + + /* a 32bit off_t is handled by _long directly */ + if (sizeof(l) == 4) return buffer_copy_long(b, l); + + if (!b) return -1; + + b->used = 0; + + if (buffer_prepare_append(b, 32)) return -1; + + s = b->ptr + b->used; + + if (l < 0) { + sign = 1; + l = -l; + } + + for (i = 0; l > 9; l /= 10, i++) { + s[i] = '0' + (l % 10); + } + + s[i] = '0' + l; + if (sign) { + s[++i] = '-'; + } + s[i+1] = '\0'; + b->used = i + 2; + + /* swap bytes again :) */ + if (i > 0) { + int li = i; + for (; i > li / 2; i--) { + char c; + + c = s[i]; + s[i] = s[li - i]; + s[li - i] = c; + } + } + + return 0; +} + +int buffer_append_off_t(buffer *b, off_t l) { + int i, sign = 0; + char *s; + + /* a 32bit off_t is handled by _long directly */ + if (sizeof(l) == 4) return buffer_append_long(b, l); + + if (!b) return -1; + + /* the buffer is empty, fallback to copy */ + if (b->used == 0) { + SEGFAULT(); + } + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + if (buffer_prepare_append(b, 32)) return -1; + + s = b->ptr + b->used - 1; + + if (l < 0) { + sign = 1; + l = -l; + } + + for (i = 0; l > 9; l /= 10, i++) { + s[i] = '0' + (l % 10); + } + + s[i] = '0' + l; + if (sign) { + s[++i] = '-'; + } + s[i+1] = '\0'; + b->used += i + 1; + + /* swap bytes again :) */ + if (i > 0) { + int li = i; + for (; i > li / 2; i--) { + char c; + + c = s[i]; + s[i] = s[li - i]; + s[li - i] = c; + } + } + + return 0; +} + +char int2hex(char c) { + return hex_chars[(c & 0x0F)]; +} + +/* converts hex char (0-9, A-Z, a-z) to decimal. + * returns 0xFF on invalid input. + */ +char hex2int(unsigned char hex) { + hex = hex - '0'; + if (hex > 9) { + hex = (hex + '0' - 1) | 0x20; + hex = hex - 'a' + 11; + } + if (hex > 15) + hex = 0xFF; + + return hex; +} + + +/** + * init the buffer + * + */ + +buffer_array* buffer_array_init(void) { + buffer_array *b; + + b = malloc(sizeof(*b)); + + assert(b); + b->ptr = NULL; + b->size = 0; + b->used = 0; + + return b; +} + +/** + * free the buffer_array + * + */ + +void buffer_array_free(buffer_array *b) { + size_t i; + if (!b) return; + + for (i = 0; i < b->size; i++) { + if (b->ptr[i]) buffer_free(b->ptr[i]); + } + free(b->ptr); + free(b); +} + +buffer *buffer_array_append_get_buffer(buffer_array *b) { + size_t i; + if (b->size == 0) { + b->size = 16; + b->ptr = malloc(sizeof(*b->ptr) * b->size); + assert(b->ptr); + for (i = 0; i < b->size; i++) { + b->ptr[i] = NULL; + } + } else if (b->size == b->used) { + b->size += 16; + b->ptr = realloc(b->ptr, sizeof(*b->ptr) * b->size); + assert(b->ptr); + for (i = b->used; i < b->size; i++) { + b->ptr[i] = NULL; + } + } + + if (b->ptr[b->used] == NULL) { + b->ptr[b->used] = buffer_init(); + } + + b->ptr[b->used]->used = 0; + + return b->ptr[b->used++]; +} + + +char * buffer_search_string_len(buffer *b, const char *needle, size_t len) { + size_t i; + if (len == 0) return NULL; + if (needle == NULL) return NULL; + + if (b->used < len) return NULL; + + for(i = 0; i < b->used - len; i++) { + if (0 == memcmp(b->ptr + i, needle, len)) { + return b->ptr + i; + } + } + + return NULL; +} + +buffer *buffer_init_string(const char *str) { + buffer *b = buffer_init(); + + buffer_copy_string(b, str); + + return b; +} + +int buffer_is_empty(buffer *b) { + return (b->used == 0); +} + +/** + * check if two buffer contain the same data + * + * this is a optimized 32/64bit compare function. + * + * it is assumed that the leftmost byte have the most equality. + * That why the comparision is done right to left + * + */ + +int buffer_is_equal(buffer *a, buffer *b) { + size_t i; + + if (a->used != b->used) return 0; + if (a->used == 0) return 1; + + for (i = a->used - 1; i < a->used && i % (sizeof(size_t)); i --) { + if (a->ptr[i] != b->ptr[i]) return 0; + } + + for (i -= (sizeof(size_t)); i < a->used; i -= (sizeof(size_t))) { + if (*((size_t *)(a->ptr + i)) != + *((size_t *)(b->ptr + i))) return 0; + } + + return 1; +} + +int buffer_is_equal_string(buffer *a, const char *s, size_t b_len) { + buffer b; + + b.ptr = (char *)s; + b.used = b_len + 1; + + return buffer_is_equal(a, &b); +} + +/* simple-assumption: + * + * most parts are equal and doing a case conversion needs time + * + */ +int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len) { + size_t ndx = 0, max_ndx; + size_t *al, *bl; + size_t mask = sizeof(*al) - 1; + + al = (size_t *)a; + bl = (size_t *)b; + + /* is the alignment correct ? */ + if ( ((size_t)al & mask) == 0 && + ((size_t)bl & mask) == 0 ) { + + max_ndx = ((a_len < b_len) ? a_len : b_len) & ~mask; + + for (; ndx < max_ndx; ndx += sizeof(*al)) { + if (*al != *bl) break; + al++; bl++; + + } + + } + + a = (char *)al; + b = (char *)bl; + + max_ndx = ((a_len < b_len) ? a_len : b_len); + + for (; ndx < max_ndx; ndx++) { + char a1 = *a++, b1 = *b++; + + if (a1 != b1) { + if ((a1 >= 'A' && a1 <= 'Z') && (b1 >= 'a' && b1 <= 'z')) + a1 |= 32; + else if ((a1 >= 'a' && a1 <= 'z') && (b1 >= 'A' && b1 <= 'Z')) + b1 |= 32; + if ((a1 - b1) != 0) return (a1 - b1); + + } + } + + return 0; +} + + +/** + * check if the rightmost bytes of the string are equal. + * + * + */ + +int buffer_is_equal_right_len(buffer *b1, buffer *b2, size_t len) { + /* no, len -> equal */ + if (len == 0) return 1; + + /* len > 0, but empty buffers -> not equal */ + if (b1->used == 0 || b2->used == 0) return 0; + + /* buffers too small -> not equal */ + if (b1->used - 1 < len || b1->used - 1 < len) return 0; + + if (0 == strncmp(b1->ptr + b1->used - 1 - len, + b2->ptr + b2->used - 1 - len, len)) { + return 1; + } + + return 0; +} + +int buffer_copy_string_hex(buffer *b, const char *in, size_t in_len) { + size_t i; + + /* BO protection */ + if (in_len * 2 < in_len) return -1; + + buffer_prepare_copy(b, in_len * 2 + 1); + + for (i = 0; i < in_len; i++) { + b->ptr[b->used++] = hex_chars[(in[i] >> 4) & 0x0F]; + b->ptr[b->used++] = hex_chars[in[i] & 0x0F]; + } + b->ptr[b->used++] = '\0'; + + return 0; +} + + +int buffer_append_string_hex(buffer *b, const char *in, size_t in_len) { + size_t i; + + /* BO protection */ + if (in_len * 2 < in_len) return -1; + + if (b->used > 0) { + if (b->ptr[b->used-1] == '\0') b->used--; + } + + buffer_prepare_append(b, in_len * 2 + 1); + + for (i = 0; i < in_len; i++) { + b->ptr[b->used++] = hex_chars[(in[i] >> 4) & 0x0F]; + b->ptr[b->used++] = hex_chars[in[i] & 0x0F]; + } + b->ptr[b->used++] = '\0'; + + return 0; +} + +int buffer_append_string_url_encoded(buffer *b, const char *s) { + unsigned char *ds, *d; + size_t d_len; + + if (!s || !b) return -1; + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + + /* count to-be-encoded-characters */ + for (ds = (unsigned char *)s, d_len = 0; *ds; ds++) { + if (*ds < 32 || *ds > 126) { + d_len += 3; + } else { + switch (*ds) { + case '$': + case '&': + case '+': + case ',': + case '/': + case ':': + case ';': + case '=': + case '?': + case '@': + case ' ': + case '#': + case '%': + case '<': + case '>': + case '"': + case '\'': + d_len += 3; + break; + default: + d_len ++; + break; + } + } + } + + if (buffer_prepare_append(b, d_len)) return -1; + + for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0; *ds; ds++) { + if (*ds < 32 || *ds > 126) { + d[d_len++] = '%'; + d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F]; + d[d_len++] = hex_chars[(*ds) & 0x0F]; + } else { + switch (*ds) { + case '$': + case '&': + case '+': + case ',': + case '/': + case ':': + case ';': + case '=': + case '?': + case '@': + case ' ': + case '#': + case '%': + case '<': + case '>': + case '"': + case '\'': + d[d_len++] = '%'; + d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F]; + d[d_len++] = hex_chars[(*ds) & 0x0F]; + break; + default: + d[d_len++] = *ds; + break; + } + } + } + + b->ptr[b->used + d_len - 1] = '\0'; + + b->used += d_len; + + return 0; +} + +int buffer_append_string_html_encoded(buffer *b, const char *s) { + unsigned char *ds, *d; + size_t d_len; + + if (!s || !b) return -1; + + if (b->ptr[b->used - 1] != '\0') { + SEGFAULT(); + } + + + /* count to-be-encoded-characters */ + for (ds = (unsigned char *)s, d_len = 0; *ds; ds++) { + switch (*ds) { + case '>': + case '<': + d_len += 4; + break; + case '&': + d_len += 5; + break; + default: + d_len++; + break; + } + } + + if (buffer_prepare_append(b, d_len)) return -1; + + for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0; *ds; ds++) { + switch (*ds) { + case '>': + d[d_len++] = '&'; + d[d_len++] = 'g'; + d[d_len++] = 't'; + d[d_len++] = ';'; + + break; + case '<': + d[d_len++] = '&'; + d[d_len++] = 'l'; + d[d_len++] = 't'; + d[d_len++] = ';'; + + break; + case '&': + d[d_len++] = '&'; + d[d_len++] = 'a'; + d[d_len++] = 'm'; + d[d_len++] = 'p'; + d[d_len++] = ';'; + + break; + + default: + d[d_len++] = *ds; + break; + } + } + + b->ptr[b->used + d_len - 1] = '\0'; + b->used += d_len; + + return 0; +} + +/* decodes url-special-chars inplace. + * ignores %00 (null-byte). + */ +int buffer_urldecode(buffer *url) { + unsigned char high, low; + const char *src; + char *dst; + + if (!url || !url->ptr) return -1; + + src = (const char*) url->ptr; + dst = (char*) url->ptr; + + while ((*src) != '\0') { +#if 1 + if (*src == '+') { + *dst = ' '; + } else +#endif + if (*src == '%') { + *dst = '%'; + + high = hex2int(*(src + 1)); + if (high != 0xFF) { + low = hex2int(*(src + 2)); + if (low != 0xFF) { + high = (high << 4) | low; + + /* map control-characters out */ + if (high < 32 || high == 127) high = '_'; + + *dst = high; + src += 2; + } + } + } else { + *dst = *src; + } + + dst++; + src++; + } + + *dst = '\0'; + url->used = (dst - url->ptr) + 1; + + return 0; +} + +int buffer_path_simplify(dot_stack *stack, buffer *out, buffer *in) { + char *last_slash, *slash; + size_t i; + + /* + * /./ -> / + * ^/../ -> / + * /abc/../ -> / + */ + + stack->used = 0; + + for (last_slash = in->ptr; NULL != (slash = strchr(last_slash, '/')); last_slash = slash + 1) { + int n; + + n = slash - last_slash; + + if ((n == 0) || /* // */ + (n == 1 && *last_slash == '.') || /* /./ */ + (n == 2 && *last_slash == '.' && *(last_slash+1) == '.')) /* /../ */ { + if (n == 2 && stack->used > 0) stack->used--; + } else { + if (stack->size == 0) { + stack->size = 16; + stack->ptr = malloc(stack->size * sizeof(*stack->ptr)); + assert(stack->ptr); + + stack->used = 0; + for (i = 0; i < stack->size; i++) { + stack->ptr[i] = malloc(sizeof(**stack->ptr)); + assert(stack->ptr[i]); + } + } else if (stack->size == stack->used) { + stack->size += 16; + stack->ptr = realloc(stack->ptr, stack->size * sizeof(*stack->ptr)); + assert(stack->ptr); + + for (i = stack->used; i < stack->size; i++) { + stack->ptr[i] = malloc(sizeof(**stack->ptr)); + assert(stack->ptr[i]); + } + } + + stack->ptr[stack->used]->start = last_slash; + stack->ptr[stack->used]->len = n + 1; + + stack->used++; + } + } + + if (stack->size == 0) { + stack->size = 16; + stack->ptr = malloc(stack->size * sizeof(*stack->ptr)); + assert(stack->ptr); + + stack->used = 0; + for (i = 0; i < stack->size; i++) { + stack->ptr[i] = malloc(sizeof(**stack->ptr)); + assert(stack->ptr[i]); + } + } else if (stack->size == stack->used) { + stack->size += 16; + stack->ptr = realloc(stack->ptr, stack->size * sizeof(*stack->ptr)); + assert(stack->ptr); + + for (i = stack->used; i < stack->size; i++) { + stack->ptr[i] = malloc(sizeof(**stack->ptr)); + assert(stack->ptr[i]); + } + } + + stack->ptr[stack->used]->start = last_slash; + stack->ptr[stack->used]->len = in->used - (last_slash - in->ptr) - 1; + + stack->used++; + + BUFFER_COPY_STRING_CONST(out, "/"); + + for (i = 0; i < stack->used; i++) { + buffer_append_string_len(out, stack->ptr[i]->start, stack->ptr[i]->len); + } + + return 0; +} + +inline int light_isdigit(int c) { + return (c >= '0' && c <= '9'); +} + +inline int light_isxdigit(int c) { + if (light_isdigit(c)) return 1; + + c |= 32; + return (c >= 'a' && c <= 'f'); +} + +inline int light_isalpha(int c) { + c |= 32; + return (c >= 'a' && c <= 'z'); +} + +inline int light_isalnum(int c) { + return light_isdigit(c) || light_isalpha(c); +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 00000000..53febed6 --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,122 @@ +#ifndef _BUFFER_H_ +#define _BUFFER_H_ + +#include <stdlib.h> +#include <sys/types.h> + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "settings.h" + +typedef struct { + char *ptr; + + size_t used; + size_t size; +} buffer; + +typedef struct { + buffer **ptr; + + size_t used; + size_t size; +} buffer_array; + +typedef struct { + char *ptr; + + size_t offset; /* input-pointer */ + + size_t used; /* output-pointer */ + size_t size; +} read_buffer; + +typedef struct { + const char *start; + size_t len; +} dot; + +typedef struct { + dot **ptr; + size_t used; + size_t size; +} dot_stack; + +buffer_array* buffer_array_init(void); +void buffer_array_free(buffer_array *b); +buffer *buffer_array_append_get_buffer(buffer_array *b); + +buffer* buffer_init(void); +buffer* buffer_init_string(const char *str); +void buffer_free(buffer *b); +void buffer_reset(buffer *b); + +int buffer_prepare_copy(buffer *b, size_t size); +int buffer_prepare_append(buffer *b, size_t size); + +int buffer_copy_string(buffer *b, const char *s); +int buffer_copy_string_len(buffer *b, const char *s, size_t s_len); +int buffer_copy_string_buffer(buffer *b, const buffer *src); +int buffer_copy_string_hex(buffer *b, const char *in, size_t in_len); + +int buffer_copy_long(buffer *b, long l); +int buffer_copy_off_t(buffer *b, off_t l); + +int buffer_copy_memory(buffer *b, const char *s, size_t s_len); + +int buffer_append_string(buffer *b, const char *s); +int buffer_append_string_len(buffer *b, const char *s, size_t s_len); +int buffer_append_string_buffer(buffer *b, const buffer *src); +int buffer_append_string_lfill(buffer *b, const char *s, size_t maxlen); +int buffer_append_string_rfill(buffer *b, const char *s, size_t maxlen); + +int buffer_append_hex(buffer *b, unsigned long len); +int buffer_append_long(buffer *b, long l); +int buffer_append_off_t(buffer *b, off_t l); + +int buffer_append_memory(buffer *b, const char *s, size_t s_len); + +char * buffer_search_string_len(buffer *b, const char *needle, size_t len); + +int buffer_is_empty(buffer *b); +int buffer_is_equal(buffer *a, buffer *b); +int buffer_is_equal_right_len(buffer *a, buffer *b, size_t len); +int buffer_is_equal_string(buffer *a, const char *s, size_t b_len); +int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len); + +int buffer_append_string_hex(buffer *b, const char *in, size_t in_len); +int buffer_append_string_url_encoded(buffer *b, const char *s); +int buffer_append_string_html_encoded(buffer *b, const char *s); + +int buffer_urldecode(buffer *url); +int buffer_path_simplify(dot_stack *stack, buffer *out, buffer *in); + +/** deprecated */ +int ltostr(char *s, long l); +char hex2int(unsigned char c); +char int2hex(char i); + +int light_isdigit(int c); +int light_isxdigit(int c); +int light_isalpha(int c); +int light_isalnum(int c); + +#define BUFFER_APPEND_STRING_CONST(x, y) \ + buffer_append_string_len(x, y, sizeof(y) - 1) + +#define BUFFER_COPY_STRING_CONST(x, y) \ + buffer_copy_string_len(x, y, sizeof(y) - 1) + +#define BUFFER_APPEND_SLASH(x) \ + if (x->used > 1 && x->ptr[x->used - 2] != '/') { BUFFER_APPEND_STRING_CONST(x, "/"); } + +#define CONST_STR_LEN(x) x, sizeof(x) - 1 +#define CONST_BUF_LEN(x) x->ptr, x->used - 1 + + +#define SEGFAULT() abort() +#define UNUSED(x) ( (void)(x) ) + +#endif diff --git a/src/chunk.c b/src/chunk.c new file mode 100644 index 00000000..ee631c60 --- /dev/null +++ b/src/chunk.c @@ -0,0 +1,312 @@ +/** + * the network chunk-API + * + * + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> + +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include "chunk.h" + +chunkqueue *chunkqueue_init(void) { + chunkqueue *cq; + + cq = calloc(1, sizeof(*cq)); + + cq->first = NULL; + cq->last = NULL; + + cq->unused = NULL; + + return cq; +} + +static chunk *chunk_init(void) { + chunk *c; + + c = calloc(1, sizeof(*c)); + + /* c->mem overlaps with c->data.file.name */ + c->data.mem = buffer_init(); + c->next = NULL; + + return c; +} + +static void chunk_free(chunk *c) { + if (!c) return; + + /* c->data.mem overlaps with c->data.file.name */ + switch (c->type) { + case MEM_CHUNK: buffer_free(c->data.mem); break; + case FILE_CHUNK: buffer_free(c->data.file.name); break; + default: break; + } + + free(c); +} + +void chunkqueue_free(chunkqueue *cq) { + chunk *c, *pc; + + if (!cq) return; + + for (c = cq->first; c; ) { + pc = c; + c = c->next; + chunk_free(pc); + } + + for (c = cq->unused; c; ) { + pc = c; + c = c->next; + chunk_free(pc); + } + + free(cq); +} + +static chunk *chunkqueue_get_unused_chunk(chunkqueue *cq) { + chunk *c; + + /* check if we have a unused chunk */ + if (!cq->unused) { + c = chunk_init(); + } else { + /* take the first element from the list (a stack) */ + c = cq->unused; + cq->unused = c->next; + c->next = NULL; + } + + return c; +} + +static int chunkqueue_prepend_chunk(chunkqueue *cq, chunk *c) { + c->next = cq->first; + cq->first = c; + + if (cq->last == NULL) { + cq->last = c; + } + + return 0; +} + +static int chunkqueue_append_chunk(chunkqueue *cq, chunk *c) { + if (cq->last) { + cq->last->next = c; + } + cq->last = c; + + if (cq->first == NULL) { + cq->first = c; + } + + return 0; +} + +void chunkqueue_reset(chunkqueue *cq) { + /* move everything to the unused queue */ + + if (cq->last == NULL) return; + + cq->last->next = cq->unused; + cq->unused = cq->first; + + /* disconnect active chain */ + cq->first = cq->last = NULL; +} + +int chunkqueue_append_file(chunkqueue *cq, buffer *fn, off_t offset, off_t len) { + chunk *c; + + if (len == 0) return 0; + + c = chunkqueue_get_unused_chunk(cq); + + c->type = FILE_CHUNK; + + buffer_copy_string_buffer(c->data.file.name, fn); + c->data.file.offset = offset; + c->data.file.length = len; + c->offset = 0; + + chunkqueue_append_chunk(cq, c); + + return 0; +} + +int chunkqueue_append_buffer(chunkqueue *cq, buffer *mem) { + chunk *c; + + if (mem->used == 0) return 0; + + c = chunkqueue_get_unused_chunk(cq); + c->type = MEM_CHUNK; + c->offset = 0; + buffer_copy_string_buffer(c->data.mem, mem); + + chunkqueue_append_chunk(cq, c); + + return 0; +} + +int chunkqueue_prepend_buffer(chunkqueue *cq, buffer *mem) { + chunk *c; + + if (mem->used == 0) return 0; + + c = chunkqueue_get_unused_chunk(cq); + c->type = MEM_CHUNK; + c->offset = 0; + buffer_copy_string_buffer(c->data.mem, mem); + + chunkqueue_prepend_chunk(cq, c); + + return 0; +} + +int chunkqueue_append_mem(chunkqueue *cq, const char * mem, size_t len) { + chunk *c; + + if (len == 0) return 0; + + c = chunkqueue_get_unused_chunk(cq); + c->type = MEM_CHUNK; + c->offset = 0; + buffer_copy_string_len(c->data.mem, mem, len - 1); + + chunkqueue_append_chunk(cq, c); + + return 0; +} + +buffer * chunkqueue_get_prepend_buffer(chunkqueue *cq) { + chunk *c; + + c = chunkqueue_get_unused_chunk(cq); + + c->type = MEM_CHUNK; + c->offset = 0; + buffer_reset(c->data.mem); + + chunkqueue_prepend_chunk(cq, c); + + return c->data.mem; +} + +buffer *chunkqueue_get_append_buffer(chunkqueue *cq) { + chunk *c; + + c = chunkqueue_get_unused_chunk(cq); + + c->type = MEM_CHUNK; + c->offset = 0; + buffer_reset(c->data.mem); + + chunkqueue_append_chunk(cq, c); + + return c->data.mem; +} + +off_t chunkqueue_length(chunkqueue *cq) { + off_t len = 0; + chunk *c; + + for (c = cq->first; c; c = c->next) { + switch (c->type) { + case MEM_CHUNK: + len += c->data.mem->used ? c->data.mem->used - 1 : 0; + break; + case FILE_CHUNK: + len += c->data.file.length; + break; + default: + break; + } + } + + return len; +} + +off_t chunkqueue_written(chunkqueue *cq) { + off_t len = 0; + chunk *c; + + for (c = cq->first; c; c = c->next) { + switch (c->type) { + case MEM_CHUNK: + case FILE_CHUNK: + len += c->offset; + break; + default: + break; + } + } + + return len; +} + +int chunkqueue_is_empty(chunkqueue *cq) { + return cq->first ? 0 : 1; +} + +#ifdef DEBUG_CHUNK + +static int write_chunkqueue(int fd, chunkqueue *c) { + UNUSED(fd); + UNUSED(c); + + return 0; +} + +int main(int argc, char **argv) { + chunkqueue *c; + buffer *b, *fn; + + UNUSED(argc); + UNUSED(argv); + + c = chunkqueue_init(); + + fn = buffer_init_string("server.c"); + + chunkqueue_append_file(c, fn, 0, 10); + chunkqueue_append_file(c, fn, 10, 10); + chunkqueue_append_file(c, fn, 20, 10); + + write_chunkqueue(STDERR_FILENO, c); + chunkqueue_reset(c); + + b = buffer_init(); + buffer_copy_string(b, "\ntest string mit vielen Zeichen\n"); + chunkqueue_append_buffer(c, b); + + write_chunkqueue(STDERR_FILENO, c); + chunkqueue_reset(c); + + chunkqueue_append_file(c, fn, 0, 10); + buffer_copy_string(b, "\ntest string mit vielen Zeichen\n"); + chunkqueue_append_buffer(c, b); + chunkqueue_append_file(c, fn, 10, 10); + chunkqueue_append_file(c, fn, 20, 10); + chunkqueue_append_file(c, fn, 50, 40); + + write_chunkqueue(STDERR_FILENO, c); + chunkqueue_reset(c); + + chunkqueue_free(c); + + return 0; +} +#endif diff --git a/src/chunk.h b/src/chunk.h new file mode 100644 index 00000000..9d5ed25a --- /dev/null +++ b/src/chunk.h @@ -0,0 +1,56 @@ +#ifndef _CHUNK_H_ +#define _CHUNK_H_ + +#include "buffer.h" + +typedef struct chunk { + /* ok, this one is tricky: + * + * MEM_CHUNK + * b: the chunk it self + * FILE_CHUNK + * b: a buffer for the filename + */ + + enum { UNUSED_CHUNK, MEM_CHUNK, FILE_CHUNK } type; + + union { + buffer *mem; + struct { + buffer *name; + off_t offset; + off_t length; + } file; + } data; + + /* how many bytes are already handled */ + + off_t offset; + + struct chunk *next; +} chunk; + +typedef struct { + chunk *first; + chunk *last; + + chunk *unused; +} chunkqueue; + +chunkqueue *chunkqueue_init(void); +int chunkqueue_append_file(chunkqueue *c, buffer *fn, off_t offset, off_t len); +int chunkqueue_append_mem(chunkqueue *c, const char *mem, size_t len); +int chunkqueue_append_buffer(chunkqueue *c, buffer *mem); +int chunkqueue_prepend_buffer(chunkqueue *c, buffer *mem); + +buffer * chunkqueue_get_append_buffer(chunkqueue *c); +buffer * chunkqueue_get_prepend_buffer(chunkqueue *c); + +off_t chunkqueue_length(chunkqueue *c); +off_t chunkqueue_written(chunkqueue *c); +void chunkqueue_free(chunkqueue *c); +void chunkqueue_reset(chunkqueue *c); + +int chunkqueue_is_empty(chunkqueue *c); + +#endif diff --git a/src/config.c b/src/config.c new file mode 100644 index 00000000..27feb6ee --- /dev/null +++ b/src/config.c @@ -0,0 +1,1025 @@ +#include <sys/stat.h> + +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <assert.h> + +#include "server.h" +#include "log.h" +#include "stream.h" +#include "plugin.h" +#ifdef USE_LICENSE +#include "license.h" +#endif + +#include "configparser.h" +#include "configfile.h" + +/* handle global options */ + +int config_insert_values_global(server *srv, array *ca, const config_values_t cv[]) { + size_t i; + data_unset *du; + + for (i = 0; cv[i].key; i++) { + data_string *touched; + + if (NULL == (du = array_get_element(ca, cv[i].key))) { + /* no found */ + + continue; + } + + /* touched */ + touched = data_string_init(); + + buffer_copy_string(touched->value, ""); + buffer_copy_string_buffer(touched->key, du->key); + + array_insert_unique(srv->config_touched, (data_unset *)touched); + } + + return config_insert_values_internal(srv, ca, cv); +} + +/* parse config array */ + +int config_insert_values_internal(server *srv, array *ca, const config_values_t cv[]) { + size_t i; + data_unset *du; + + for (i = 0; cv[i].key; i++) { + + if (NULL == (du = array_get_element(ca, cv[i].key))) { + /* no found */ + + continue; + } + + switch (cv[i].type) { + case T_CONFIG_ARRAY: + if (du->type == TYPE_ARRAY) { + size_t j; + data_array *da = (data_array *)du; + + for (j = 0; j < da->value->used; j++) { + if (da->value->data[j]->type == TYPE_STRING) { + data_string *ds = data_string_init(); + + buffer_copy_string_buffer(ds->value, ((data_string *)(da->value->data[j]))->value); + buffer_copy_string_buffer(ds->key, ((data_string *)(da->value->data[j]))->key); + + array_insert_unique(cv[i].destination, (data_unset *)ds); + } else { + log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", cv[i].key, "[", da->value->data[i]->key, "](string)"); + + return -1; + } + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", "unexpected type for key: ", cv[i].key, "array of strings"); + + return -1; + } + break; + case T_CONFIG_STRING: + if (du->type == TYPE_STRING) { + data_string *ds = (data_string *)du; + + buffer_copy_string_buffer(cv[i].destination, ds->value); + } else { + log_error_write(srv, __FILE__, __LINE__, "ssss", "unexpected type for key: ", cv[i].key, "(string)", "\"...\""); + + return -1; + } + break; + case T_CONFIG_SHORT: + switch(du->type) { + case TYPE_INTEGER: { + data_integer *di = (data_integer *)du; + + *((unsigned short *)(cv[i].destination)) = di->value; + break; + } + case TYPE_STRING: { + data_string *ds = (data_string *)du; + + log_error_write(srv, __FILE__, __LINE__, "ssbss", "unexpected type for key: ", cv[i].key, ds->value, "(short)", "0 ... 65535"); + + return -1; + } + default: + log_error_write(srv, __FILE__, __LINE__, "ssdss", "unexpected type for key: ", cv[i].key, du->type, "(short)", "0 ... 65535"); + return -1; + } + break; + case T_CONFIG_BOOLEAN: + if (du->type == TYPE_STRING) { + data_string *ds = (data_string *)du; + + if (0 == strcmp(ds->value->ptr, "enable")) { + *((unsigned short *)(cv[i].destination)) = 1; + } else if (0 == strcmp(ds->value->ptr, "disable")) { + *((unsigned short *)(cv[i].destination)) = 0; + } else { + log_error_write(srv, __FILE__, __LINE__, "ssbs", "ERROR: unexpected value for key:", cv[i].key, ds->value, "(enable|disable)"); + + return -1; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: unexpected type for key:", cv[i].key, "(string)", "\"(enable|disable)\""); + + return -1; + } + break; + case T_CONFIG_LOCAL: + case T_CONFIG_UNSET: + break; + case T_CONFIG_DEPRECATED: + log_error_write(srv, __FILE__, __LINE__, "ssss", "ERROR: found deprecated key:", cv[i].key, "-", (char *)(cv[i].destination)); + + srv->config_deprecated = 1; + + break; + } + } + return 0; +} + +static int config_insert(server *srv) { + size_t i; + int ret = 0; + + config_values_t cv[] = { + { "server.bind", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 0 */ + { "server.errorlog", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 1 */ + { "server.errorfile-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 2 */ + { "server.chroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 3 */ + { "server.username", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 4 */ + { "server.groupname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 5 */ + { "server.port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 6 */ + { "server.tag", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ + { "server.use-ipv6", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 8 */ + { "server.modules", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_SERVER }, /* 9 */ + + { "server.event-handler", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 10 */ + { "server.pid-file", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 11 */ + { "server.max-request-size", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ + { "server.max-worker", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 13 */ + { "server.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ + { "server.dir-listing", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ + { "server.indexfiles", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "server.max-keep-alive-requests", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ + { "server.name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 18 */ + { "server.max-keep-alive-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 19 */ + + { "server.max-read-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 20 */ + { "server.max-write-idle", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 21 */ + { "server.error-handler-404", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 22 */ + { "server.max-fds", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_SERVER }, /* 23 */ + { "server.follow-symlink", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 24 */ + { "server.kbytes-per-second", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 25 */ + { "connection.kbytes-per-second", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 26 */ + { "mimetype.use-xattr", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 27 */ + { "mimetype.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 28 */ + { "ssl.pemfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, /* 29 */ + + { "ssl.engine", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 30 */ + + { "debug.log-file-not-found", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 31 */ + { "debug.log-request-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 32 */ + { "debug.log-response-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 33 */ + { "debug.log-request-header", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 34 */ + + { "server.protocol-http11", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 35 */ + { "debug.log-request-header-on-error", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 36 */ + { "debug.log-state-handling", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_SERVER }, /* 37 */ + + + { "server.host", "use server.bind instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.docroot", "use server.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.virtual-root", "load mod_simple_vhost and use simple-vhost.server-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.virtual-default-host", "load mod_simple_vhost and use simple-vhost.default-host instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.virtual-docroot", "load mod_simple_vhost and use simple-vhost.document-root instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.userid", "use server.username instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.groupid", "use server.groupname instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + { "server.use-keep-alive", "use server.max-keep-alive-requests = 0 instead", T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_UNSET }, + + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + + /* 0 */ + cv[0].destination = srv->srvconf.bindhost; + cv[1].destination = srv->srvconf.error_logfile; + cv[2].destination = srv->srvconf.errorfile_prefix; + cv[3].destination = srv->srvconf.changeroot; + cv[4].destination = srv->srvconf.username; + cv[5].destination = srv->srvconf.groupname; + cv[6].destination = &(srv->srvconf.port); + + cv[9].destination = srv->srvconf.modules; + cv[10].destination = srv->srvconf.event_handler; + cv[11].destination = srv->srvconf.pid_file; + + cv[13].destination = &(srv->srvconf.max_worker); + cv[23].destination = &(srv->srvconf.max_fds); + cv[36].destination = &(srv->srvconf.log_request_header_on_error); + cv[37].destination = &(srv->srvconf.log_state_handling); + + srv->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + assert(srv->config_storage); + + for (i = 0; i < srv->config_context->used; i++) { + specific_config *s; + + s = calloc(1, sizeof(specific_config)); + assert(s); + s->document_root = buffer_init(); + s->dir_listing = 0; + s->indexfiles = array_init(); + s->mimetypes = array_init(); + s->server_name = buffer_init(); + s->ssl_pemfile = buffer_init(); + s->error_handler = buffer_init(); + s->server_tag = buffer_init(); + s->max_keep_alive_requests = 128; + s->max_keep_alive_idle = 30; + s->max_read_idle = 60; + s->max_write_idle = 360; + s->use_xattr = 0; + s->is_ssl = 0; + s->use_ipv6 = 0; + s->follow_symlink = 1; + s->kbytes_per_second = 0; + s->allow_http11 = 1; + s->global_kbytes_per_second = 0; + s->global_bytes_per_second_cnt = 0; + s->global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt; + + cv[7].destination = s->server_tag; + cv[8].destination = &(s->use_ipv6); + + + cv[12].destination = &(s->max_request_size); + /* 13 max-worker */ + cv[14].destination = s->document_root; + cv[15].destination = &(s->dir_listing); + cv[16].destination = s->indexfiles; + cv[17].destination = &(s->max_keep_alive_requests); + cv[18].destination = s->server_name; + cv[19].destination = &(s->max_keep_alive_idle); + cv[20].destination = &(s->max_read_idle); + cv[21].destination = &(s->max_write_idle); + cv[22].destination = s->error_handler; + cv[24].destination = &(s->follow_symlink); + /* 23 -> max-fds */ + cv[25].destination = &(s->global_kbytes_per_second); + cv[26].destination = &(s->kbytes_per_second); + cv[27].destination = &(s->use_xattr); + cv[28].destination = s->mimetypes; + cv[29].destination = s->ssl_pemfile; + cv[30].destination = &(s->is_ssl); + + cv[31].destination = &(s->log_file_not_found); + cv[32].destination = &(s->log_request_handling); + cv[33].destination = &(s->log_response_header); + cv[34].destination = &(s->log_request_header); + + cv[35].destination = &(s->allow_http11); + + srv->config_storage[i] = s; + + if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) { + break; + } + } + + return ret; + +} + +int config_check_cond(server *srv, connection *con, data_config *dc) { + buffer *l; + server_socket *srv_sock = con->srv_socket; + /* pass the rules */ + + l = srv->empty_string; + + if (0 == strcmp(dc->comp_key->ptr, "HTTPhost")) { + l = con->uri.authority; + } else if (0 == strcmp(dc->comp_key->ptr, "HTTPurl")) { + l = con->uri.path; + } else if (0 == strcmp(dc->comp_key->ptr, "SERVERsocket")) { + l = srv_sock->srv_token; + } else if (0 == strcmp(dc->comp_key->ptr, "HTTPreferer")) { + data_string *ds; + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Referer"))) { + l = ds->value; + } + } else if (0 == strcmp(dc->comp_key->ptr, "HTTPcookie")) { + data_string *ds; + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) { + l = ds->value; + } + } else if (0 == strcmp(dc->comp_key->ptr, "HTTPuseragent")) { + data_string *ds; + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "User-Agent"))) { + l = ds->value; + } + } else { + return 0; + } + + switch(dc->cond) { + case CONFIG_COND_NE: + case CONFIG_COND_EQ: + if (buffer_is_equal(l, dc->match.string)) { + return (dc->cond == CONFIG_COND_EQ) ? 1 : 0; + } else { + return (dc->cond == CONFIG_COND_EQ) ? 0 : 1; + } + break; +#ifdef HAVE_PCRE_H + case CONFIG_COND_NOMATCH: + case CONFIG_COND_MATCH: { +#define N 10 + int ovec[N * 3]; + int n; + + n = pcre_exec(dc->match.regex, NULL, l->ptr, l->used - 1, 0, 0, ovec, N * 3); + + if (n > 0) { + return (dc->cond == CONFIG_COND_MATCH) ? 1 : 0; + } else { + return (dc->cond == CONFIG_COND_MATCH) ? 0 : 1; + } + + break; + } +#endif + default: + /* no way */ + break; + } + + return 0; +} + +#define PATCH(x) con->conf.x = s->x +int config_setup_connection(server *srv, connection *con) { + specific_config *s = srv->config_storage[0]; + + PATCH(allow_http11); + PATCH(mimetypes); + PATCH(document_root); + PATCH(dir_listing); + PATCH(indexfiles); + PATCH(max_keep_alive_requests); + PATCH(max_keep_alive_idle); + PATCH(max_read_idle); + PATCH(max_write_idle); + PATCH(use_xattr); + PATCH(error_handler); + PATCH(follow_symlink); + PATCH(server_tag); + PATCH(kbytes_per_second); + PATCH(global_kbytes_per_second); + PATCH(global_bytes_per_second_cnt); + con->conf.global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt; + buffer_copy_string_buffer(con->server_name, s->server_name); + + PATCH(log_request_header); + PATCH(log_response_header); + PATCH(log_request_handling); + PATCH(log_file_not_found); + + return 0; +} + +int config_patch_connection(server *srv, connection *con, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + specific_config *s = srv->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.document-root"))) { + PATCH(document_root); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.dir-listing"))) { + PATCH(dir_listing); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.error-handler-404"))) { + PATCH(error_handler); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.indexfiles"))) { + PATCH(indexfiles); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.assign"))) { + PATCH(mimetypes); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-requests"))) { + PATCH(max_keep_alive_requests); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-keep-alive-idle"))) { + PATCH(max_keep_alive_idle); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-write-idle"))) { + PATCH(max_write_idle); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.max-read-idle"))) { + PATCH(max_read_idle); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("mimetype.use-xattr"))) { + PATCH(use_xattr); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.pemfile"))) { + PATCH(ssl_pemfile); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssl.engine"))) { + PATCH(is_ssl); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.follow-symlink"))) { + PATCH(follow_symlink); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.name"))) { + buffer_copy_string_buffer(con->server_name, s->server_name); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.tag"))) { + PATCH(server_tag); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("connection.kbytes-per-second"))) { + PATCH(kbytes_per_second); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-request-handling"))) { + PATCH(log_request_handling); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-request-header"))) { + PATCH(log_request_header); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-response-header"))) { + PATCH(log_response_header); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("debug.log-file-not-found"))) { + PATCH(log_file_not_found); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.protocol-http11"))) { + PATCH(allow_http11); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("server.kbytes-per-second"))) { + PATCH(global_kbytes_per_second); + PATCH(global_bytes_per_second_cnt); + con->conf.global_bytes_per_second_cnt_ptr = &s->global_bytes_per_second_cnt; + } + } + } + + return 0; +} +#undef PATCH +typedef struct { + int foo; + int bar; + + char *input; + size_t offset; + size_t size; + + int line_pos; + int line; + + int in_key; + int in_brace; + int in_cond; +} tokenizer_t; + +static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer *token) { + int tid = 0; + size_t i; + + for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) { + char c = t->input[t->offset]; + char *start = NULL; + + switch (c) { + case '=': + if (t->in_brace) { + if (t->input[t->offset + 1] == '>') { + t->offset += 2; + + buffer_copy_string(token, "=>"); + + tid = TK_ARRAY_ASSIGN; + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "use => for assignments in arrays"); + return -1; + } + } else if (t->in_cond) { + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + + buffer_copy_string(token, "=="); + + tid = TK_EQ; + } else if (t->input[t->offset + 1] == '~') { + t->offset += 2; + + buffer_copy_string(token, "=~"); + + tid = TK_MATCH; + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "only =~ and == are allow in the condition"); + return -1; + } + } else if (t->in_key) { + tid = TK_ASSIGN; + + buffer_copy_string_len(token, t->input + t->offset, 1); + + t->offset++; + t->line_pos++; + t->in_key = 0; + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "unexpected equal-sign: ="); + return -1; + } + + break; + case '!': + if (t->in_cond) { + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + + buffer_copy_string(token, "!="); + + tid = TK_NE; + } else if (t->input[t->offset + 1] == '~') { + t->offset += 2; + + buffer_copy_string(token, "!~"); + + tid = TK_NOMATCH; + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "only !~ and != are allow in the condition"); + return -1; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "unexpected exclamation-marks: !"); + return -1; + } + + break; + case '\t': + case ' ': + t->offset++; + t->line_pos++; + break; + case '\r': + if (t->in_brace == 0) { + if (t->input[t->offset + 1] == '\n') { + t->in_key = 1; + t->offset += 2; + + tid = TK_EOL; + t->line++; + t->line_pos = 1; + + buffer_copy_string(token, "(EOL)"); + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "CR without LF"); + return 0; + } + } else { + t->offset++; + t->line_pos++; + } + break; + case '\n': + if (t->in_brace == 0) { + t->in_key = 1; + + tid = TK_EOL; + + buffer_copy_string(token, "(EOL)"); + } + t->line++; + t->line_pos = 1; + t->offset++; + break; + case ',': + if (t->in_brace > 0) { + tid = TK_COMMA; + + buffer_copy_string(token, "(COMMA)"); + } + + t->offset++; + t->line_pos++; + break; + case '"': + /* search for the terminating " */ + start = t->input + t->offset + 1; + buffer_copy_string(token, ""); + + for (i = 1; t->input[t->offset + i]; i++) { + if (t->input[t->offset + i] == '\\' && + t->input[t->offset + i + 1] == '"') { + + buffer_append_string_len(token, start, t->input + t->offset + i - start); + + start = t->input + t->offset + i + 1; + + /* skip the " */ + i++; + continue; + } + + + if (t->input[t->offset + i] == '"') { + tid = TK_STRING; + + buffer_append_string_len(token, start, t->input + t->offset + i - start); + + break; + } + } + + if (t->input[t->offset + i] == '\0') { + /* ERROR */ + + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "missing closing quote"); + + return -1; + } + + t->offset += i + 1; + t->line_pos += i + 1; + + break; + case '(': + t->offset++; + t->in_brace++; + + tid = TK_LPARAN; + + buffer_copy_string(token, "("); + break; + case ')': + t->offset++; + t->in_brace--; + + tid = TK_RPARAN; + + buffer_copy_string(token, ")"); + break; + case '$': + t->offset++; + + tid = TK_DOLLAR; + t->in_cond = 1; + t->in_key = 0; + + buffer_copy_string(token, "$"); + + break; + case '{': + t->offset++; + + tid = TK_LCURLY; + t->in_key = 1; + t->in_cond = 0; + + buffer_copy_string(token, "{"); + + break; + + case '}': + t->offset++; + + tid = TK_RCURLY; + + buffer_copy_string(token, "}"); + + break; + case '[': + t->offset++; + + tid = TK_LBRACKET; + + buffer_copy_string(token, "["); + + break; + + case ']': + t->offset++; + + tid = TK_RBRACKET; + + buffer_copy_string(token, "]"); + + break; + case '#': + for (i = 1; t->input[t->offset + i] && + (t->input[t->offset + i] != '\n' && t->input[t->offset + i] != '\r'); + i++); + + t->offset += i; + + break; + default: + if (t->in_key) { + /* the key might consist of [-.0-9a-z] */ + for (i = 0; t->input[t->offset + i] && + (isalnum((unsigned char)t->input[t->offset + i]) || + t->input[t->offset + i] == '.' || + t->input[t->offset + i] == '-' + ); i++); + + if (i && t->input[t->offset + i]) { + tid = TK_LKEY; + buffer_copy_string_len(token, t->input + t->offset, i); + + t->offset += i; + t->line_pos += i; + } else { + /* ERROR */ + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "invalid character in lvalue"); + return -1; + } + } else if (t->in_cond) { + for (i = 0; t->input[t->offset + i] && + (isalpha((unsigned char)t->input[t->offset + i]) + ); i++); + + if (i && t->input[t->offset + i]) { + tid = TK_SRVVARNAME; + buffer_copy_string_len(token, t->input + t->offset, i); + + t->offset += i; + t->line_pos += i; + } else { + /* ERROR */ + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "invalid character in condition"); + return -1; + } + } else { + if (isdigit((unsigned char)c)) { + /* take all digits */ + for (i = 0; t->input[t->offset + i] && isdigit((unsigned char)t->input[t->offset + i]); i++); + + /* was there it least a digit ? */ + if (i && t->input[t->offset + i]) { + tid = TK_INTEGER; + + buffer_copy_string_len(token, t->input + t->offset, i); + + t->offset += i; + t->line_pos += i; + } else { + /* ERROR */ + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "unexpected EOF"); + + return -1; + } + } else { + /* ERROR */ + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t->line, "pos:", t->line_pos, + "invalid value field"); + + return -1; + } + } + break; + } + } + + if (tid) { + *token_id = tid; + + return 1; + } else if (t->offset < t->size) { + fprintf(stderr, "%s.%d: %d, %s\n", + __FILE__, __LINE__, + tid, token->ptr); + } + return 0; +} + +int config_read(server *srv, const char *fn) { + stream s; + tokenizer_t t; + void *pParser; + int token_id; + buffer *token; + config_t context; + data_config *dc; + int ret; + buffer *bfn = buffer_init_string(fn); + + if (0 != stream_open(&s, bfn)) { + buffer_free(bfn); + + log_error_write(srv, __FILE__, __LINE__, "ssss", + "opening configfile ", fn, "failed:", strerror(errno)); + return -1; + } + + buffer_free(bfn); + + t.input = s.start; + t.offset = 0; + t.size = s.size; + t.line = 1; + t.line_pos = 1; + + t.in_key = 1; + t.in_brace = 0; + t.in_cond = 0; + + context.ok = 1; + context.config = srv->config_context; + + dc = data_config_init(); + buffer_copy_string(dc->key, "global"); + array_insert_unique(srv->config_context, (data_unset *)dc); + + context.ctx_name = dc->key; + context.ctx_config = dc->value; + + /* default context */ + srv->config = dc->value; + + pParser = configparserAlloc( malloc ); + token = buffer_init(); + while((1 == (ret = config_tokenizer(srv, &t, &token_id, token))) && context.ok) { + configparser(pParser, token_id, token, &context); + + token = buffer_init(); + } + configparser(pParser, 0, token, &context); + configparserFree(pParser, free ); + + buffer_free(token); + + stream_close(&s); + + if (ret == -1) { + log_error_write(srv, __FILE__, __LINE__, "s", + "configfile parser failed"); + return -1; + } + + if (context.ok == 0) { + log_error_write(srv, __FILE__, __LINE__, "sdsds", + "line:", t.line, "pos:", t.line_pos, + "parser failed somehow near here"); + return -1; + } + + if (0 != config_insert(srv)) { + return -1; + } + + if (NULL != (dc = (data_config *)array_get_element(srv->config_context, "global"))) { + srv->config = dc->value; + } else { + return -1; + } + + return 0; +} + +int config_set_defaults(server *srv) { + size_t i; + specific_config *s = srv->config_storage[0]; + + struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] = + { + /* - poll is most reliable + * - select works everywhere + * - linux-* are experimental + */ +#ifdef USE_POLL + { FDEVENT_HANDLER_POLL, "poll" }, +#endif +#ifdef USE_SELECT + { FDEVENT_HANDLER_SELECT, "select" }, +#endif +#ifdef USE_LINUX_EPOLL + { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" }, +#endif +#ifdef USE_LINUX_SIGIO + { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" }, +#endif +#ifdef USE_SOLARIS_DEVPOLL + { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" }, +#endif +#ifdef USE_FREEBSD_KQUEUE + { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" }, +#endif + { FDEVENT_HANDLER_UNSET, NULL } + }; + +#ifdef USE_LICENSE + license_t *l; + + if (srv->srvconf.license->used == 0) { + /* license is missing */ + return -1; + } + + l = license_init(); + + if (0 != license_parse(l, srv->srvconf.license)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "parsing license information failed", srv->srvconf.license); + + license_free(l); + return -1; + } + if (!license_is_valid(l)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "license is not valid"); + + license_free(l); + return -1; + } + license_free(l); +#endif + if (srv->srvconf.port == 0) { + srv->srvconf.port = s->is_ssl ? 443 : 80; + } + + if (srv->srvconf.event_handler->used == 0) { + /* choose a good default + * + * the event_handler list is sorted by 'goodness' + * taking the first available should be the best solution + */ + srv->event_handler = event_handlers[0].et; + + if (FDEVENT_HANDLER_UNSET == srv->event_handler) { + log_error_write(srv, __FILE__, __LINE__, "s", + "sorry, there is no event handler for this system"); + + return -1; + } + } else { + /* + * User override + */ + + for (i = 0; event_handlers[i].name; i++) { + if (0 == strcmp(event_handlers[i].name, srv->srvconf.event_handler->ptr)) { + srv->event_handler = event_handlers[i].et; + break; + } + } + + if (FDEVENT_HANDLER_UNSET == srv->event_handler) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "the selected event-handler in unknown or not supported:", + srv->srvconf.event_handler ); + + return -1; + } + } + + if (s->is_ssl) { + if (s->ssl_pemfile->used == 0) { + /* PEM file is require */ + + log_error_write(srv, __FILE__, __LINE__, "s", + "ssl.pemfile has to be set"); + return -1; + } + +#ifndef USE_OPENSSL + log_error_write(srv, __FILE__, __LINE__, "s", + "ssl support is missing, recompile with --with-openssl"); + + return -1; +#endif + } + + return 0; +} diff --git a/src/configfile.h b/src/configfile.h new file mode 100644 index 00000000..55a13d5e --- /dev/null +++ b/src/configfile.h @@ -0,0 +1,18 @@ +#ifndef _CONFIG_PARSER_H_ +#define _CONFIG_PARSER_H_ + +#include "array.h" +#include "buffer.h" + +typedef struct { + int ok; + array *config; + buffer *ctx_name; + array *ctx_config; +} config_t; + +void *configparserAlloc(void *(*mallocProc)(size_t)); +void configparserFree(void *p, void (*freeProc)(void*)); +void configparser(void *yyp, int yymajor, buffer *yyminor, config_t *ctx); + +#endif diff --git a/src/configparser.h b/src/configparser.h new file mode 100644 index 00000000..c7e86ae1 --- /dev/null +++ b/src/configparser.h @@ -0,0 +1,19 @@ +#define TK_EOL 1 +#define TK_ASSIGN 2 +#define TK_LKEY 3 +#define TK_STRING 4 +#define TK_INTEGER 5 +#define TK_LPARAN 6 +#define TK_RPARAN 7 +#define TK_COMMA 8 +#define TK_ARRAY_ASSIGN 9 +#define TK_LCURLY 10 +#define TK_RCURLY 11 +#define TK_DOLLAR 12 +#define TK_SRVVARNAME 13 +#define TK_LBRACKET 14 +#define TK_RBRACKET 15 +#define TK_EQ 16 +#define TK_MATCH 17 +#define TK_NE 18 +#define TK_NOMATCH 19 diff --git a/src/configparser.y b/src/configparser.y new file mode 100644 index 00000000..3d424ce3 --- /dev/null +++ b/src/configparser.y @@ -0,0 +1,164 @@ +%token_prefix TK_ +%token_type {buffer *} +%extra_argument {config_t *ctx} +%name configparser + +%include { +#include <assert.h> +#include "config.h" +#include "configfile.h" +#include "buffer.h" +#include "array.h" +} + +%parse_failure { + ctx->ok = 0; +} + +input ::= metalines. +metalines ::= metalines metaline. +metalines ::= . +metaline ::= varline. +metaline ::= condline. +metaline ::= EOL. + +%type value {data_unset *} +%type aelement {data_unset *} +%type aelements {array *} +%type array {array *} +%type cond {config_cond_t } +%token_destructor { buffer_free($$); } + +varline ::= key(A) ASSIGN value(B). { + buffer_copy_string_buffer(B->key, A); + array_insert_unique(ctx->ctx_config, B); + buffer_free(A); +} + +key(A) ::= LKEY(B). { + A = B; + B = NULL; +} + +value(A) ::= STRING(B). { + A = (data_unset *)data_string_init(); + buffer_copy_string_buffer(((data_string *)(A))->value, B); + buffer_free(B); +} + +value(A) ::= INTEGER(B). { + A = (data_unset *)data_integer_init(); + ((data_integer *)(A))->value = strtol(B->ptr, NULL, 10); + buffer_free(B); +} +value(A) ::= array(B). { + A = (data_unset *)data_array_init(); + array_free(((data_array *)(A))->value); + ((data_array *)(A))->value = B; +} +array(A) ::= LPARAN aelements(B) RPARAN. { + A = B; + B = NULL; +} + +aelements(A) ::= aelements(C) COMMA aelement(B). { + array_insert_unique(C, B); + + A = C; +} + +aelements(A) ::= aelements(C) COMMA. { + A = C; +} + +aelements(A) ::= aelement(B). { + A = array_init(); + array_insert_unique(A, B); +} + +aelement(A) ::= value(B). { + A = B; + B = NULL; +} +aelement(A) ::= STRING(B) ARRAY_ASSIGN value(C). { + buffer_copy_string_buffer(C->key, B); + buffer_free(B); + + A = C; + C = NULL; +} +condline ::= context LCURLY metalines RCURLY EOL. { + data_config *dc; + + dc = (data_config *)array_get_element(ctx->config, "global"); + assert(dc); + ctx->ctx_name = dc->key; + ctx->ctx_config = dc->value; +} + +context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). { + data_config *dc; + buffer *b; + + b = buffer_init(); + buffer_copy_string_buffer(b, B); + buffer_append_string_buffer(b, C); + buffer_append_string_buffer(b, D); + buffer_append_long(b, E); + + if (NULL != (dc = (data_config *)array_get_element(ctx->config, b->ptr))) { + ctx->ctx_name = dc->key; + ctx->ctx_config = dc->value; + } else { + dc = data_config_init(); + + buffer_copy_string_buffer(dc->key, b); + buffer_copy_string_buffer(dc->comp_key, B); + buffer_append_string_buffer(dc->comp_key, C); + dc->cond = E; + + switch(E) { + case CONFIG_COND_NE: + case CONFIG_COND_EQ: + dc->match.string = buffer_init_string(D->ptr); + break; +#ifdef HAVE_PCRE_H + case CONFIG_COND_NOMATCH: + case CONFIG_COND_MATCH: { + const char *errptr; + int erroff; + + if (NULL == (dc->match.regex = + pcre_compile(D->ptr, 0, &errptr, &erroff, NULL))) { + dc->match.string = buffer_init_string(errptr); + dc->cond = CONFIG_COND_UNSET; + } + break; + } +#endif + default: + break; + } + + array_insert_unique(ctx->config, (data_unset *)dc); + + ctx->ctx_name = dc->key; + ctx->ctx_config = dc->value; + } + buffer_free(b); + buffer_free(B); + buffer_free(C); + buffer_free(D); +} +cond(A) ::= EQ. { + A = CONFIG_COND_EQ; +} +cond(A) ::= MATCH. { + A = CONFIG_COND_MATCH; +} +cond(A) ::= NE. { + A = CONFIG_COND_NE; +} +cond(A) ::= NOMATCH. { + A = CONFIG_COND_NOMATCH; +} diff --git a/src/connections.c b/src/connections.c new file mode 100644 index 00000000..5f1ff544 --- /dev/null +++ b/src/connections.c @@ -0,0 +1,1585 @@ +#include <sys/stat.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <assert.h> + +#include "buffer.h" +#include "server.h" +#include "log.h" +#include "connections.h" +#include "fdevent.h" + +#include "request.h" +#include "response.h" +#include "network.h" +#include "http_chunk.h" +#include "file_cache.h" +#include "joblist.h" + +#include "plugin.h" + +#ifdef USE_OPENSSL +# include <openssl/ssl.h> +# include <openssl/err.h> +#endif + +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#include "sys-socket.h" + +const char *connection_get_state(connection_state_t state) { + switch (state) { + case CON_STATE_CONNECT: return "connect"; + case CON_STATE_READ: return "read"; + case CON_STATE_READ_POST: return "readpost"; + case CON_STATE_WRITE: return "write"; + case CON_STATE_CLOSE: return "close"; + case CON_STATE_ERROR: return "error"; + case CON_STATE_HANDLE_REQUEST: return "handle-req"; + case CON_STATE_REQUEST_START: return "req-start"; + case CON_STATE_REQUEST_END: return "req-end"; + case CON_STATE_RESPONSE_START: return "resp-start"; + case CON_STATE_RESPONSE_END: return "resp-end"; + default: return "(unknown)"; + } +} + +const char *connection_get_short_state(connection_state_t state) { + switch (state) { + case CON_STATE_CONNECT: return "."; + case CON_STATE_READ: return "r"; + case CON_STATE_READ_POST: return "R"; + case CON_STATE_WRITE: return "W"; + case CON_STATE_CLOSE: return "C"; + case CON_STATE_ERROR: return "E"; + case CON_STATE_HANDLE_REQUEST: return "h"; + case CON_STATE_REQUEST_START: return "q"; + case CON_STATE_REQUEST_END: return "Q"; + case CON_STATE_RESPONSE_START: return "s"; + case CON_STATE_RESPONSE_END: return "S"; + default: return "x"; + } +} + +static connection *connections_get_new_connection(server *srv) { + connections *conns = srv->conns; + size_t i; + + if (conns->size == 0) { + conns->size = 128; + conns->ptr = NULL; + conns->ptr = malloc(sizeof(*conns->ptr) * conns->size); + for (i = 0; i < conns->size; i++) { + conns->ptr[i] = connection_init(srv); + } + } else if (conns->size == conns->used) { + conns->size += 128; + conns->ptr = realloc(conns->ptr, sizeof(*conns->ptr) * conns->size); + + for (i = conns->used; i < conns->size; i++) { + conns->ptr[i] = connection_init(srv); + } + } + + connection_reset(srv, conns->ptr[conns->used]); +#if 0 + fprintf(stderr, "%s.%d: add: ", __FILE__, __LINE__); + for (i = 0; i < conns->used + 1; i++) { + fprintf(stderr, "%d ", conns->ptr[i]->fd); + } + fprintf(stderr, "\n"); +#endif + + conns->ptr[conns->used]->ndx = conns->used; + return conns->ptr[conns->used++]; +} + +static int connection_del(server *srv, connection *con) { + size_t i; + connections *conns = srv->conns; + connection *temp; + + if (con == NULL) return -1; + + if (-1 == con->ndx) return -1; + + i = con->ndx; + + /* not last element */ + + if (i != conns->used - 1) { + temp = conns->ptr[i]; + conns->ptr[i] = conns->ptr[conns->used - 1]; + conns->ptr[conns->used - 1] = temp; + + conns->ptr[i]->ndx = i; + conns->ptr[conns->used - 1]->ndx = -1; + } + + conns->used--; + + con->ndx = -1; +#if 0 + fprintf(stderr, "%s.%d: del: (%d)", __FILE__, __LINE__, conns->used); + for (i = 0; i < conns->used; i++) { + fprintf(stderr, "%d ", conns->ptr[i]->fd); + } + fprintf(stderr, "\n"); +#endif + return 0; +} + +int connection_close(server *srv, connection *con) { +#ifdef USE_OPENSSL + server_socket *srv_sock = con->srv_socket; +#endif + +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + if (con->ssl) SSL_free(con->ssl); + con->ssl = NULL; + } +#endif + + fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd); + fdevent_unregister(srv->ev, con->fd); +#ifdef __WIN32 + if (closesocket(con->fd)) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "(warning) close:", con->fd, strerror(errno)); + } +#else + if (close(con->fd)) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "(warning) close:", con->fd, strerror(errno)); + } +#endif + + srv->cur_fds--; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", + "closed()", con->fd); +#endif + + connection_del(srv, con); + connection_set_state(srv, con, CON_STATE_CONNECT); + + return 0; +} + +static void dump_packet(const unsigned char *data, size_t len) { + size_t i, j; + + if (len == 0) return; + + for (i = 0; i < len; i++) { + if (i % 16 == 0) fprintf(stderr, " "); + + fprintf(stderr, "%02x ", data[i]); + + if ((i + 1) % 16 == 0) { + fprintf(stderr, " "); + for (j = 0; j <= i % 16; j++) { + unsigned char c; + + if (i-15+j >= len) break; + + c = data[i-15+j]; + + fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.'); + } + + fprintf(stderr, "\n"); + } + } + + if (len % 16 != 0) { + for (j = i % 16; j < 16; j++) { + fprintf(stderr, " "); + } + + fprintf(stderr, " "); + for (j = i & ~0xf; j < len; j++) { + unsigned char c; + + c = data[j]; + fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.'); + } + fprintf(stderr, "\n"); + } +} + +static int connection_handle_read(server *srv, connection *con) { + int len; + buffer *b; +#ifdef USE_OPENSSL + server_socket *srv_sock = con->srv_socket; +#endif + + b = chunkqueue_get_append_buffer(con->read_queue); + buffer_prepare_copy(b, 4096); + +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + len = SSL_read(con->ssl, b->ptr, b->size - 1); + } else { + len = read(con->fd, b->ptr, b->size - 1); + } +#elif defined(__WIN32) + len = recv(con->fd, b->ptr, b->size - 1, 0); +#else + len = read(con->fd, b->ptr, b->size - 1); +#endif + + if (len < 0) { + con->is_readable = 0; + +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + int r; + + switch ((r = SSL_get_error(con->ssl, len))) { + case SSL_ERROR_WANT_READ: + return 0; + default: + log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", + r, ERR_error_string(ERR_get_error(), NULL)); + break; + } + } else { + if (errno == EAGAIN) return 0; + if (errno == EINTR) { + /* we have been interrupted before we could read */ + con->is_readable = 1; + return 0; + } + + if (errno != ECONNRESET) { + /* expected for keep-alive */ + log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno); + } + } +#else + if (errno == EAGAIN) return 0; + if (errno == EINTR) { + /* we have been interrupted before we could read */ + con->is_readable = 1; + return 0; + } + + if (errno != ECONNRESET) { + /* expected for keep-alive */ + log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno); + } +#endif + connection_set_state(srv, con, CON_STATE_ERROR); + + return -1; + } else if (len == 0) { + con->is_readable = 0; + /* the other end close the connection -> KEEP-ALIVE */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "s", + "connection closed: remote site closed unexpectedly"); +#endif + connection_set_state(srv, con, CON_STATE_ERROR); + return -1; + } else if ((size_t)len < b->size - 1) { + /* we got less then expected, wait for the next fd-event */ + + con->is_readable = 0; + } + + b->used = len; + b->ptr[b->used++] = '\0'; + + con->bytes_read += len; +#if 0 + dump_packet(b->ptr, len); +#endif + + return 0; +} + +static int connection_handle_write_prepare(server *srv, connection *con) { + struct stat st; + int s_len; + + switch(con->mode) { + case DIRECT: + switch(con->http_status) { + case 400: /* class: header + custom body */ + case 401: + case 403: + case 404: + case 408: + case 411: + case 416: + case 500: + case 501: + case 503: + case 505: { + con->file_finished = 1; + + /* rewrite the filename */ + + /* FIXME: use con.physical.errorfile + * + * + */ + buffer_reset(con->physical.path); + + if (srv->srvconf.errorfile_prefix->used) { + buffer_copy_string_buffer(con->physical.path, srv->srvconf.errorfile_prefix); + buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status)); + } + + if ((con->physical.path->used <= 1) || + (-1 == (stat(con->physical.path->ptr, &st)))) { + buffer *b; + + buffer_reset(con->physical.path); + + b = chunkqueue_get_append_buffer(con->write_queue); + + /* build default error-page */ + buffer_copy_string(b, + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + " <head>\n" + " <title>"); + buffer_append_long(b, con->http_status); + buffer_append_string(b, " - "); + buffer_append_string(b, get_http_status_name(con->http_status)); + + buffer_append_string(b, + "</title>\n" + " </head>\n" + " <body>\n" + " <h1>"); + buffer_append_long(b, con->http_status); + buffer_append_string(b, " - "); + buffer_append_string(b, get_http_status_name(con->http_status)); + + buffer_append_string(b,"</h1>\n" + " </body>\n" + "</html>\n" + ); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + } else { + /* get content-type */ + size_t k; + s_len = con->physical.path->used - 1; + + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + int ct_len = ds->key->used - 1; + + if (s_len < ct_len || + ds->key->used == 0) continue; + + if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->key->ptr, ct_len)) { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(ds->value)); + break; + } + } + + if (k == con->conf.mimetypes->used) { + /* the error message should be HTML */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + } + } + } + /* fall through */ + + case 200: /* class: header + body */ + if (con->physical.path->used) { + con->file_finished = 1; + + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), con->physical.path); + + connection_set_state(srv, con, CON_STATE_ERROR); + + return -1; + } + + if (S_ISREG(con->fce->st.st_mode)) { + if (con->request.http_method == HTTP_METHOD_GET || + con->request.http_method == HTTP_METHOD_POST) { + http_chunk_append_file(srv, con, con->physical.path, 0, con->fce->st.st_size); + con->response.content_length = http_chunkqueue_length(srv, con); + } else if (con->request.http_method == HTTP_METHOD_HEAD) { + con->response.content_length = con->fce->st.st_size; + } else { + connection_set_state(srv, con, CON_STATE_ERROR); + return -1; + } + + http_response_write_header(srv, con, + con->response.content_length, + con->fce->st.st_mtime); + + + } else { + /* why the heck ? */ + + log_error_write(srv, __FILE__, __LINE__, "sb", + "connection closed: no regular-file to send:", + con->physical.path); + + con->file_finished = 1; + } + } else { + if (con->file_finished) { + con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con); + } + + /* disable keep-alive if size-info for the body is missing */ + if ((con->parsed_response & HTTP_CONTENT_LENGTH) && + ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) { + con->keep_alive = 0; + } + + if (con->request.http_method == HTTP_METHOD_HEAD) { + chunkqueue_reset(con->write_queue); + } + + http_response_write_header(srv, con, + con->response.content_length, + 0); + + } + break; + + case 206: /* write_queue is already prepared */ + http_response_write_header(srv, con, + con->response.content_length, + 0); + con->file_finished = 1; + break; + case 302: + con->file_finished = 1; + + con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con); + + http_response_write_header(srv, con, + con->response.content_length, + 0); + + break; + case 205: /* class: header only */ + case 301: + case 304: + default: + /* disable chunked encoding again as we have no body */ + con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; + chunkqueue_reset(con->write_queue); + + http_response_write_header(srv, con, 0, 0); + con->file_finished = 1; + break; + } + + break; + default: + if (con->request.http_method == HTTP_METHOD_HEAD || + con->http_status == 301 || + con->http_status == 304 || + con->http_status == 205) { + /* remove possible chunks */ + con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED; + chunkqueue_reset(con->write_queue); + } + + if (0 == (con->parsed_response & HTTP_CONNECTION)) { + /* (f)cgi did'nt send Connection: header + * + * shall we ? + */ + if (((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) && + (con->parsed_response & HTTP_CONTENT_LENGTH) == 0) { + /* without content_length, no keep-alive */ + + con->keep_alive = 0; + } + } else { + /* a subrequest disable keep-alive although the client wanted it */ + if (con->keep_alive && !con->response.keep_alive) { + con->keep_alive = 0; + + /* FIXME: we have to drop the Connection: Header from the subrequest */ + } + } + + con->response.content_length = (ssize_t)http_chunkqueue_length(srv, con); + http_response_write_basic_header(srv, con); + + break; + } + + return 0; +} + +static int connection_handle_write(server *srv, connection *con) { + switch(network_write_chunkqueue(srv, con, con->write_queue)) { + case 0: + if (con->file_finished) { + connection_set_state(srv, con, CON_STATE_RESPONSE_END); + joblist_append(srv, con); + } + break; + case -1: /* error on our side */ + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed: write failed on fd", con->fd); + connection_set_state(srv, con, CON_STATE_ERROR); + joblist_append(srv, con); + break; + case -2: /* remote close */ + connection_set_state(srv, con, CON_STATE_ERROR); + joblist_append(srv, con); + break; + case 1: + con->is_writable = 0; + + /* not finished yet -> WRITE */ + break; + } + + return 0; +} + + + +connection *connection_init(server *srv) { + connection *con; + + UNUSED(srv); + + con = calloc(1, sizeof(*con)); + + con->fd = 0; + con->ndx = -1; + con->fde_ndx = -1; + con->bytes_written = 0; + con->bytes_read = 0; + con->bytes_header = 0; + + +#define CLEAN(x) \ + con->x = buffer_init(); + + CLEAN(request.uri); + CLEAN(request.request_line); + CLEAN(request.request); + CLEAN(request.pathinfo); + CLEAN(request.content); + + CLEAN(request.orig_uri); + + CLEAN(uri.scheme); + CLEAN(uri.authority); + CLEAN(uri.path); + CLEAN(uri.path_raw); + CLEAN(uri.query); + + CLEAN(physical.doc_root); + CLEAN(physical.path); + CLEAN(physical.rel_path); + CLEAN(physical.etag); + CLEAN(parse_request); + + CLEAN(authed_user); + CLEAN(server_name); + CLEAN(error_handler); + +#undef CLEAN + con->write_queue = chunkqueue_init(); + con->read_queue = chunkqueue_init(); + con->request.headers = array_init(); + con->response.headers = array_init(); + con->environment = array_init(); + + /* init plugin specific connection structures */ + + con->plugin_ctx = calloc(srv->plugins.used + 1, sizeof(void *)); + + config_setup_connection(srv, con); + + return con; +} + +void connections_free(server *srv) { + connections *conns = srv->conns; + size_t i; + + for (i = 0; i < conns->size; i++) { + connection *con = conns->ptr[i]; + + connection_reset(srv, con); + + chunkqueue_free(con->write_queue); + chunkqueue_free(con->read_queue); + array_free(con->request.headers); + array_free(con->response.headers); + array_free(con->environment); + +#define CLEAN(x) \ + buffer_free(con->x); + + CLEAN(request.uri); + CLEAN(request.request_line); + CLEAN(request.request); + CLEAN(request.pathinfo); + CLEAN(request.content); + + CLEAN(request.orig_uri); + + CLEAN(uri.scheme); + CLEAN(uri.authority); + CLEAN(uri.path); + CLEAN(uri.path_raw); + CLEAN(uri.query); + + CLEAN(physical.doc_root); + CLEAN(physical.path); + CLEAN(physical.etag); + CLEAN(physical.rel_path); + CLEAN(parse_request); + + CLEAN(authed_user); + CLEAN(server_name); + CLEAN(error_handler); +#undef CLEAN + free(con->plugin_ctx); + + free(con); + } + + free(conns->ptr); +} + + +int connection_reset(server *srv, connection *con) { + size_t i; + + plugins_call_connection_reset(srv, con); + + con->is_readable = 1; + con->is_writable = 1; + con->http_status = 0; + con->file_finished = 0; + con->file_started = 0; + con->got_response = 0; + + con->parsed_response = 0; + + con->bytes_written = 0; + con->bytes_written_cur_second = 0; + con->bytes_read = 0; + con->bytes_header = 0; + + con->request.http_method = HTTP_METHOD_UNSET; + con->request.http_version = HTTP_VERSION_UNSET; + + con->request.http_if_modified_since = NULL; + con->request.http_if_none_match = NULL; + + con->response.keep_alive = 0; + con->response.content_length = -1; + con->response.transfer_encoding = 0; + + con->mode = DIRECT; + +#define CLEAN(x) \ + if (con->x) buffer_reset(con->x); + + CLEAN(request.uri); + CLEAN(request.request_line); + CLEAN(request.pathinfo); + CLEAN(request.content); + CLEAN(request.request); + + CLEAN(request.orig_uri); + + CLEAN(uri.scheme); + CLEAN(uri.authority); + CLEAN(uri.path); + CLEAN(uri.path_raw); + CLEAN(uri.query); + + CLEAN(physical.doc_root); + CLEAN(physical.path); + CLEAN(physical.rel_path); + CLEAN(physical.etag); + + CLEAN(parse_request); + + CLEAN(authed_user); + CLEAN(server_name); + CLEAN(error_handler); +#undef CLEAN + +#define CLEAN(x) \ + if (con->x) con->x->used = 0; + +#undef CLEAN + +#define CLEAN(x) \ + con->request.x = NULL; + + CLEAN(http_host); + CLEAN(http_range); + CLEAN(http_content_type); +#undef CLEAN + con->request.content_length = 0; + + array_reset(con->request.headers); + array_reset(con->response.headers); + array_reset(con->environment); + + chunkqueue_reset(con->write_queue); + + if (con->fce) { + file_cache_entry_release(srv, con, con->fce); + con->fce = NULL; + } + + for (i = 0; i < srv->plugins.used; i++) { + con->plugin_ctx[0] = NULL; + } + + con->header_len = 0; + con->in_error_handler = 0; + + config_setup_connection(srv, con); + + return 0; +} + +/** + * + * search for \r\n\r\n + * + * this is a special 32bit version which is using a sliding window for + * the comparisions + * + * how it works: + * + * b: 'abcdefg' + * rnrn: 'cdef' + * + * cmpbuf: abcd != cdef + * cmpbuf: bcde != cdef + * cmpbuf: cdef == cdef -> return &c + * + * cmpbuf and rnrn are treated as 32bit uint and bit-ops are used to + * maintain cmpbuf and rnrn + * + */ + +char *buffer_search_rnrn(buffer *b) { + uint32_t cmpbuf, rnrn; + char *cp; + size_t i; + + if (b->used < 4) return NULL; + + rnrn = ('\r' << 24) | ('\n' << 16) | + ('\r' << 8) | ('\n' << 0); + + cmpbuf = (b->ptr[0] << 24) | (b->ptr[1] << 16) | + (b->ptr[2] << 8) | (b->ptr[3] << 0); + + cp = b->ptr + 4; + for (i = 0; i < b->used - 4; i++) { + if (cmpbuf == rnrn) return cp - 4; + + cmpbuf = (cmpbuf << 8 | *(cp++)) & 0xffffffff; + } + + return NULL; +} + +int connection_handle_read_state(server *srv, connection *con) { + int ostate = con->state; + char *h_term = NULL; + chunk *c; + chunkqueue *cq = con->read_queue; + + if (con->is_readable) { + con->read_idle_ts = srv->cur_ts; + + if (0 != connection_handle_read(srv, con)) { + return -1; + } + } + + /* move the empty chunks out of the way */ + for (c = cq->first; c; c = cq->first) { + assert(c != c->next); + + if (c->data.mem->used == 0) { + cq->first = c->next; + c->next = cq->unused; + cq->unused = c; + + if (cq->first == NULL) cq->last = NULL; + + c = cq->first; + } else { + break; + } + } + + /* nothing to handle */ + if (cq->first == NULL) return 0; + + switch(ostate) { + case CON_STATE_READ: + /* prepare con->request.request */ + c = cq->first; + + /* check if we need the full package */ + if (con->request.request->used == 0) { + buffer b; + + b.ptr = c->data.mem->ptr + c->offset; + b.used = c->data.mem->used - c->offset; + + if (NULL != (h_term = buffer_search_rnrn(&b))) { + /* \r\n\r\n found + * - copy everything incl. the terminator to request.request + */ + + buffer_copy_string_len(con->request.request, + b.ptr, + h_term - b.ptr + 4); + + /* the buffer has been read up to the terminator */ + c->offset += h_term - b.ptr + 4; + } else { + /* not found, copy everything */ + buffer_copy_string_len(con->request.request, c->data.mem->ptr + c->offset, c->data.mem->used - c->offset - 1); + c->offset = c->data.mem->used - 1; + } + } else { + /* have to take care of overlapping header terminators */ + + size_t l = con->request.request->used - 2; + char *s = con->request.request->ptr; + buffer b; + + b.ptr = c->data.mem->ptr + c->offset; + b.used = c->data.mem->used - c->offset; + + if (con->request.request->used - 1 > 3 && + c->data.mem->used > 1 && + s[l-2] == '\r' && + s[l-1] == '\n' && + s[l-0] == '\r' && + c->data.mem->ptr[0] == '\n') { + buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 1); + c->offset += 1; + + h_term = con->request.request->ptr; + } else if (con->request.request->used - 1 > 2 && + c->data.mem->used > 2 && + s[l-1] == '\r' && + s[l-0] == '\n' && + c->data.mem->ptr[0] == '\r' && + c->data.mem->ptr[1] == '\n') { + buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 2); + c->offset += 2; + + h_term = con->request.request->ptr; + } else if (con->request.request->used - 1 > 1 && + c->data.mem->used > 3 && + s[l-0] == '\r' && + c->data.mem->ptr[0] == '\n' && + c->data.mem->ptr[1] == '\r' && + c->data.mem->ptr[2] == '\n') { + buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, 3); + c->offset += 3; + + h_term = con->request.request->ptr; + } else if (NULL != (h_term = buffer_search_string_len(&b, "\r\n\r\n", 4))) { + /* \r\n\r\n found + * - copy everything incl. the terminator to request.request + */ + + buffer_append_string_len(con->request.request, + c->data.mem->ptr + c->offset, + c->offset + h_term - b.ptr + 4); + + /* the buffer has been read up to the terminator */ + c->offset += h_term - b.ptr + 4; + } else { + /* not found, copy everything */ + buffer_append_string_len(con->request.request, c->data.mem->ptr + c->offset, c->data.mem->used - c->offset - 1); + c->offset = c->data.mem->used - 1; + } + } + + if (c->offset + 1 == c->data.mem->used) { + /* chunk is empty, move it to unused */ + cq->first = c->next; + c->next = cq->unused; + cq->unused = c; + + if (cq->first == NULL) cq->last = NULL; + + assert(c != c->next); + } + + /* con->request.request is setup up */ + if (h_term) { + connection_set_state(srv, con, CON_STATE_REQUEST_END); + } else if (chunkqueue_length(cq) > 64 * 1024) { + log_error_write(srv, __FILE__, __LINE__, "sd", "http-header larger then 64k -> disconnected", chunkqueue_length(cq)); + connection_set_state(srv, con, CON_STATE_ERROR); + } + break; + case CON_STATE_READ_POST: + for (c = cq->first; c && (con->request.content->used != con->request.content_length + 1); c = cq->first) { + off_t weWant, weHave, toRead; + + weWant = con->request.content_length - (con->request.content->used ? con->request.content->used - 1 : 0); + /* without the terminating \0 */ + + assert(c->data.mem->used); + + weHave = c->data.mem->used - c->offset - 1; + + toRead = weHave > weWant ? weWant : weHave; + + buffer_append_string_len(con->request.content, c->data.mem->ptr + c->offset, toRead); + + c->offset += toRead; + + if (c->offset + 1 >= c->data.mem->used) { + /* chunk is empty, move it to unused */ + + cq->first = c->next; + c->next = cq->unused; + cq->unused = c; + + if (cq->first == NULL) cq->last = NULL; + + assert(c != c->next); + } else { + assert(toRead); + } + } + + /* Content is ready */ + if (con->request.content->used == con->request.content_length + 1) { + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + } + + break; + } + + return 0; +} + +handler_t connection_handle_fdevent(void *s, void *context, int revents) { + server *srv = (server *)s; + connection *con = context; + + joblist_append(srv, con); + + if (revents & FDEVENT_IN) { + con->is_readable = 1; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "read-wait - done", con->fd); +#endif + } + if (revents & FDEVENT_OUT) { + con->is_writable = 1; + /* we don't need the event twice */ + } + + + if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) { + /* looks like an error */ + + /* FIXME: revents = 0x19 still means that we should read from the queue */ + if (revents & FDEVENT_HUP) { + if (con->state == CON_STATE_CLOSE) { + con->close_timeout_ts = 0; + } else { + /* sigio reports the wrong event here + * + * there was no HUP at all + */ +#ifdef USE_LINUX_SIGIO + if (srv->ev->in_sigio == 1) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed: poll() -> HUP", con->fd); + } else { + connection_set_state(srv, con, CON_STATE_ERROR); + } +#else + connection_set_state(srv, con, CON_STATE_ERROR); +#endif + + } + } else if (revents & FDEVENT_ERR) { +#ifndef USE_LINUX_SIGIO + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed: poll() -> ERR", con->fd); +#endif + connection_set_state(srv, con, CON_STATE_ERROR); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed: poll() -> ???", revents); + } + } + + if (con->state == CON_STATE_READ || + con->state == CON_STATE_READ_POST) { + connection_handle_read_state(srv, con); + } + + if (con->state == CON_STATE_WRITE && + !chunkqueue_is_empty(con->write_queue) && + con->is_writable) { + + if (-1 == connection_handle_write(srv, con)) { + connection_set_state(srv, con, CON_STATE_ERROR); + + log_error_write(srv, __FILE__, __LINE__, "ds", + con->fd, + "handle write failed."); + } else if (con->state == CON_STATE_WRITE) { + con->write_request_ts = srv->cur_ts; + } + } + + if (con->state == CON_STATE_CLOSE) { + /* flush the read buffers */ + int b; + + if (ioctl(con->fd, FIONREAD, &b)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "ioctl() failed", strerror(errno)); + } + + if (b > 0) { + char buf[1024]; + log_error_write(srv, __FILE__, __LINE__, "sdd", + "CLOSE-read()", con->fd, b); + + /* */ + read(con->fd, buf, sizeof(buf)); + } else { + /* nothing to read */ + + con->close_timeout_ts = 0; + } + } + + return HANDLER_FINISHED; +} + + +connection *connection_accept(server *srv, server_socket *srv_socket) { + int accepted_requests = 0; + /* accept everything */ + + /* search an empty place */ + int cnt; + sock_addr cnt_addr; + socklen_t cnt_len; + /* accept it and register the fd */ + + cnt_len = sizeof(cnt_addr); + + if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) { + if ((errno != EAGAIN) && + (errno != EINTR)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "accept failed: ", strerror(errno)); + } + return NULL; + } else { + connection *con; + + srv->cur_fds++; + + accepted_requests++; + /* ok, we have the connection, register it */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", + "appected()", cnt); +#endif + srv->con_opened++; + + con = connections_get_new_connection(srv); + + con->fd = cnt; + con->fde_ndx = -1; +#if 0 + gettimeofday(&(con->start_tv), NULL); +#endif + fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con); + + connection_set_state(srv, con, CON_STATE_REQUEST_START); + + con->connection_start = srv->cur_ts; + con->dst_addr = cnt_addr; + con->srv_socket = srv_socket; + + if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); + return NULL; + } +#ifdef USE_OPENSSL + /* connect FD to SSL */ + if (srv_socket->is_ssl) { + if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + ERR_error_string(ERR_get_error(), NULL)); + + return NULL; + } else { + SSL_set_accept_state(con->ssl); + } + + if (1 != (SSL_set_fd(con->ssl, cnt))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + } +#endif + return con; + } +} + +int connection_set_state(server *srv, connection *con, connection_state_t state) { + UNUSED(srv); + + con->state = state; + + return 0; +} + + +int connection_state_machine(server *srv, connection *con) { + int done = 0, r; +#ifdef USE_OPENSSL + server_socket *srv_sock = con->srv_socket; +#endif + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state at start", + con->fd, + connection_get_state(con->state)); + } + + while (done == 0) { + size_t ostate = con->state; + int b; + + switch (con->state) { + case CON_STATE_REQUEST_START: /* transient */ + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + con->request_start = srv->cur_ts; + con->read_idle_ts = srv->cur_ts; + + con->request_count++; + + connection_set_state(srv, con, CON_STATE_READ); + + break; + case CON_STATE_REQUEST_END: /* transient */ + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + if (http_request_parse(srv, con)) { + /* we have to read some data from the POST request */ + + connection_set_state(srv, con, CON_STATE_READ_POST); + + break; + } + + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + + break; + case CON_STATE_HANDLE_REQUEST: + /* + * the request is parsed + * + * decided what to do with the request + * - + * + * + */ + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + switch (r = http_response_prepare(srv, con)) { + case HANDLER_FINISHED: + if (con->http_status == 404 || + con->http_status == 403) { + /* 404 error-handler */ + + if (con->in_error_handler == 0 && + (!buffer_is_empty(con->conf.error_handler) || + !buffer_is_empty(con->error_handler))) { + /* call error-handler */ + + con->error_handler_saved_status = con->http_status; + con->http_status = 0; + + if (buffer_is_empty(con->error_handler)) { + buffer_copy_string_buffer(con->request.uri, con->conf.error_handler); + } else { + buffer_copy_string_buffer(con->request.uri, con->error_handler); + } + buffer_reset(con->physical.path); + + con->in_error_handler = 1; + + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + + done = -1; + break; + } else if (con->in_error_handler) { + /* error-handler is a 404 */ + + /* continue as normal, status is the same */ + log_error_write(srv, __FILE__, __LINE__, "sb", "error-handler not found:", con->conf.error_handler); + + con->http_status = con->error_handler_saved_status; + } + } else if (con->in_error_handler) { + /* error-handler is back and has generated content */ + /* reset the old status */ + if (con->http_status == 0) { + con->http_status = con->error_handler_saved_status; + } + } + + if (con->http_status == 0) con->http_status = 200; + + /* we have something to send, go on */ + connection_set_state(srv, con, CON_STATE_RESPONSE_START); + break; + case HANDLER_WAIT_FOR_FD: + srv->want_fds++; + + fdwaitqueue_append(srv, con); + + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + + break; + case HANDLER_COMEBACK: + done = -1; + case HANDLER_WAIT_FOR_EVENT: + /* come back here */ + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + break; + case HANDLER_ERROR: + /* something went wrong */ + connection_set_state(srv, con, CON_STATE_ERROR); + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sdd", "unknown ret-value: ", con->fd, r); + break; + } + + break; + case CON_STATE_RESPONSE_START: + /* + * the decision is done + * - create the HTTP-Response-Header + * + */ + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + connection_handle_write_prepare(srv, con); + + connection_set_state(srv, con, CON_STATE_WRITE); + break; + case CON_STATE_RESPONSE_END: /* transient */ + /* log the request */ + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + plugins_call_handle_request_done(srv, con); + + srv->con_written++; + + if (con->keep_alive) { + connection_set_state(srv, con, CON_STATE_REQUEST_START); + +#if 0 + con->request_start = srv->cur_ts; + con->read_idle_ts = srv->cur_ts; +#endif + } else { + switch(r = plugins_call_handle_connection_close(srv, con)) { + case HANDLER_GO_ON: + case HANDLER_FINISHED: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "unhandling return value", r); + break; + } + +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + switch (SSL_shutdown(con->ssl)) { + case 1: + /* done */ + break; + case 0: + /* wait for fd-event + * + * FIXME: wait for fdevent and call SSL_shutdown again + * + */ + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + ERR_error_string(ERR_get_error(), NULL)); + } + } +#endif + connection_close(srv, con); + + srv->con_closed++; + } + + connection_reset(srv, con); + + break; + case CON_STATE_CONNECT: + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + chunkqueue_reset(con->read_queue); + + con->request_count = 0; + + break; + case CON_STATE_CLOSE: + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + if (con->keep_alive) { + if (ioctl(con->fd, FIONREAD, &b)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "ioctl() failed", strerror(errno)); + } + if (b > 0) { + char buf[1024]; + log_error_write(srv, __FILE__, __LINE__, "sdd", + "CLOSE-read()", con->fd, b); + + /* */ + read(con->fd, buf, sizeof(buf)); + } else { + /* nothing to read */ + + con->close_timeout_ts = 0; + } + } else { + con->close_timeout_ts = 0; + } + + if (srv->cur_ts - con->close_timeout_ts > 1) { + connection_close(srv, con); + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed for fd", con->fd); + } + } + + break; + case CON_STATE_READ_POST: + case CON_STATE_READ: + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + connection_handle_read_state(srv, con); + break; + case CON_STATE_WRITE: + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state for fd", con->fd, connection_get_state(con->state)); + } + + /* only try to write if we have something in the queue */ + if (!chunkqueue_is_empty(con->write_queue)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "dsd", + con->fd, + "packets to write:", + con->write_queue->used); +#endif + } + if (!chunkqueue_is_empty(con->write_queue) && con->is_writable) { + if (-1 == connection_handle_write(srv, con)) { + log_error_write(srv, __FILE__, __LINE__, "ds", + con->fd, + "handle write failed."); + connection_set_state(srv, con, CON_STATE_ERROR); + } else if (con->state == CON_STATE_WRITE) { + con->write_request_ts = srv->cur_ts; + } + } + + break; + case CON_STATE_ERROR: /* transient */ + + /* even if the connection was drop we still have to write it to the access log */ + if (con->http_status) { + plugins_call_handle_request_done(srv, con); + } +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + int ret; + switch ((ret = SSL_shutdown(con->ssl))) { + case 1: + /* ok */ + break; + case 0: + SSL_shutdown(con->ssl); + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", + SSL_get_error(con->ssl, ret), + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + } +#endif + + switch(con->mode) { + case DIRECT: +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", + "emergency exit: direct", + con->fd); +#endif + break; + default: + switch(r = plugins_call_handle_connection_close(srv, con)) { + case HANDLER_GO_ON: + case HANDLER_FINISHED: + break; + default: + log_error_write(srv, __FILE__, __LINE__, ""); + break; + } + break; + } + + connection_reset(srv, con); + + /* close the connection */ + if ((con->keep_alive == 1) && + (0 == shutdown(con->fd, SHUT_WR))) { + con->close_timeout_ts = srv->cur_ts; + connection_set_state(srv, con, CON_STATE_CLOSE); + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "shutdown for fd", con->fd); + } + } else { + connection_close(srv, con); + } + + con->keep_alive = 0; + + srv->con_closed++; + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sdd", + "unknown state:", con->fd, con->state); + + break; + } + + if (done == -1) { + done = 0; + } else if (ostate == con->state) { + done = 1; + } + } + + if (srv->srvconf.log_state_handling) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "state at exit:", + con->fd, + connection_get_state(con->state)); + } + + switch(con->state) { + case CON_STATE_READ_POST: + case CON_STATE_READ: + case CON_STATE_CLOSE: + fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN); + break; + case CON_STATE_WRITE: + /* request write-fdevent only if we really need it + * - if we have data to write + * - if the socket is not writable yet + */ + if (!chunkqueue_is_empty(con->write_queue) && + (con->is_writable == 0) && + (con->traffic_limit_reached == 0)) { + fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT); + } else { + fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd); + } + break; + default: + fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd); + break; + } + + return 0; +} diff --git a/src/connections.h b/src/connections.h new file mode 100644 index 00000000..1fcfc365 --- /dev/null +++ b/src/connections.h @@ -0,0 +1,19 @@ +#ifndef _CONNECTIONS_H_ +#define _CONNECTIONS_H_ + +#include "server.h" +#include "fdevent.h" + +connection *connection_init(server *srv); +int connection_reset(server *srv, connection *con); +void connections_free(server *srv); + +connection * connection_accept(server *srv, server_socket *srv_sock); +int connection_close(server *srv, connection *con); + +int connection_set_state(server *srv, connection *con, connection_state_t state); +const char * connection_get_state(connection_state_t state); +const char * connection_get_short_state(connection_state_t state); +int connection_state_machine(server *srv, connection *con); + +#endif diff --git a/src/crc32.c b/src/crc32.c new file mode 100644 index 00000000..2b2053e1 --- /dev/null +++ b/src/crc32.c @@ -0,0 +1,84 @@ +#include "crc32.h" + +#define CRC32C(c,d) (c=(c>>8)^crc_c[(c^(d))&0xFF]) + +static const unsigned int crc_c[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + + +unsigned long +generate_crc32c(unsigned char *buffer, unsigned int length) +{ + unsigned int i; + unsigned long crc32 = ~0L; + + for (i = 0; i < length; i++){ + CRC32C(crc32, buffer[i]); + } + return ~crc32; +} + diff --git a/src/crc32.h b/src/crc32.h new file mode 100644 index 00000000..e5b1c2fd --- /dev/null +++ b/src/crc32.h @@ -0,0 +1,6 @@ +#ifndef __crc32cr_table_h__ +#define __crc32cr_table_h__ + +unsigned long generate_crc32c(unsigned char *string, unsigned int length); + +#endif diff --git a/src/data_array.c b/src/data_array.c new file mode 100644 index 00000000..6cd740c4 --- /dev/null +++ b/src/data_array.c @@ -0,0 +1,56 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" + +static void data_array_free(data_unset *d) { + data_array *ds = (data_array *)d; + + buffer_free(ds->key); + array_free(ds->value); + + free(d); +} + +static void data_array_reset(data_unset *d) { + data_array *ds = (data_array *)d; + + /* reused array elements */ + buffer_reset(ds->key); + array_reset(ds->value); +} + +static int data_array_insert_dup(data_unset *dst, data_unset *src) { + UNUSED(dst); + + src->free(src); + + return 0; +} + +static void data_array_print(data_unset *d) { + data_array *ds = (data_array *)d; + + printf("{%s:\n", ds->key->ptr); + array_print(ds->value); + printf("}"); +} + + +data_array *data_array_init(void) { + data_array *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->value = array_init(); + + ds->free = data_array_free; + ds->reset = data_array_reset; + ds->insert_dup = data_array_insert_dup; + ds->print = data_array_print; + ds->type = TYPE_ARRAY; + + return ds; +} diff --git a/src/data_config.c b/src/data_config.c new file mode 100644 index 00000000..6a01af04 --- /dev/null +++ b/src/data_config.c @@ -0,0 +1,69 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" + +static void data_config_free(data_unset *d) { + data_config *ds = (data_config *)d; + + buffer_free(ds->key); + buffer_free(ds->comp_key); + + array_free(ds->value); + + switch(ds->cond) { + case CONFIG_COND_EQ: buffer_free(ds->match.string); break; +#ifdef HAVE_PCRE_H + case CONFIG_COND_MATCH: pcre_free(ds->match.regex); break; +#endif + default: + break; + } + + free(d); +} + +static void data_config_reset(data_unset *d) { + data_config *ds = (data_config *)d; + + /* reused array elements */ + buffer_reset(ds->key); + buffer_reset(ds->comp_key); + array_reset(ds->value); +} + +static int data_config_insert_dup(data_unset *dst, data_unset *src) { + UNUSED(dst); + + src->free(src); + + return 0; +} + +static void data_config_print(data_unset *d) { + data_config *ds = (data_config *)d; + + printf("{%s:\n", ds->key->ptr); + array_print(ds->value); + printf("}"); +} + + +data_config *data_config_init(void) { + data_config *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->comp_key = buffer_init(); + ds->value = array_init(); + + ds->free = data_config_free; + ds->reset = data_config_reset; + ds->insert_dup = data_config_insert_dup; + ds->print = data_config_print; + ds->type = TYPE_CONFIG; + + return ds; +} diff --git a/src/data_count.c b/src/data_count.c new file mode 100644 index 00000000..843e3bb1 --- /dev/null +++ b/src/data_count.c @@ -0,0 +1,56 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" + +static void data_count_free(data_unset *d) { + data_count *ds = (data_count *)d; + + buffer_free(ds->key); + + free(d); +} + +static void data_count_reset(data_unset *d) { + data_count *ds = (data_count *)d; + + buffer_reset(ds->key); + + ds->count = 0; +} + +static int data_count_insert_dup(data_unset *dst, data_unset *src) { + data_count *ds_dst = (data_count *)dst; + data_count *ds_src = (data_count *)src; + + ds_dst->count += ds_src->count; + + src->free(src); + + return 0; +} + +static void data_count_print(data_unset *d) { + data_count *ds = (data_count *)d; + + printf("{%s: %d}", ds->key->ptr, ds->count); +} + + +data_count *data_count_init(void) { + data_count *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->count = 1; + + ds->free = data_count_free; + ds->reset = data_count_reset; + ds->insert_dup = data_count_insert_dup; + ds->print = data_count_print; + ds->type = TYPE_COUNT; + + return ds; +} diff --git a/src/data_fastcgi.c b/src/data_fastcgi.c new file mode 100644 index 00000000..28273752 --- /dev/null +++ b/src/data_fastcgi.c @@ -0,0 +1,56 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "array.h" +#include "fastcgi.h" + +static void data_fastcgi_free(data_unset *d) { + data_fastcgi *ds = (data_fastcgi *)d; + + buffer_free(ds->key); + buffer_free(ds->host); + + free(d); +} + +static void data_fastcgi_reset(data_unset *d) { + data_fastcgi *ds = (data_fastcgi *)d; + + buffer_reset(ds->key); + buffer_reset(ds->host); + +} + +static int data_fastcgi_insert_dup(data_unset *dst, data_unset *src) { + UNUSED(dst); + + src->free(src); + + return 0; +} + +static void data_fastcgi_print(data_unset *d) { + data_fastcgi *ds = (data_fastcgi *)d; + + printf("{%s: %s}", ds->key->ptr, ds->host->ptr); +} + + +data_fastcgi *data_fastcgi_init(void) { + data_fastcgi *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->host = buffer_init(); + ds->port = 0; + + ds->free = data_fastcgi_free; + ds->reset = data_fastcgi_reset; + ds->insert_dup = data_fastcgi_insert_dup; + ds->print = data_fastcgi_print; + ds->type = TYPE_FASTCGI; + + return ds; +} diff --git a/src/data_integer.c b/src/data_integer.c new file mode 100644 index 00000000..af6ad221 --- /dev/null +++ b/src/data_integer.c @@ -0,0 +1,53 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "array.h" + +static void data_integer_free(data_unset *d) { + data_integer *ds = (data_integer *)d; + + buffer_free(ds->key); + + free(d); +} + +static void data_integer_reset(data_unset *d) { + data_integer *ds = (data_integer *)d; + + /* reused integer elements */ + buffer_reset(ds->key); + ds->value = 0; +} + +static int data_integer_insert_dup(data_unset *dst, data_unset *src) { + UNUSED(dst); + + src->free(src); + + return 0; +} + +static void data_integer_print(data_unset *d) { + data_integer *ds = (data_integer *)d; + + printf("{%s: %d}", ds->key->ptr, ds->value); +} + + +data_integer *data_integer_init(void) { + data_integer *ds; + + ds = calloc(1, sizeof(*ds)); + + ds->key = buffer_init(); + ds->value = 0; + + ds->free = data_integer_free; + ds->reset = data_integer_reset; + ds->insert_dup = data_integer_insert_dup; + ds->print = data_integer_print; + ds->type = TYPE_INTEGER; + + return ds; +} diff --git a/src/data_string.c b/src/data_string.c new file mode 100644 index 00000000..5ce160a3 --- /dev/null +++ b/src/data_string.c @@ -0,0 +1,92 @@ +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "array.h" + +static void data_string_free(data_unset *d) { + data_string *ds = (data_string *)d; + + buffer_free(ds->key); + buffer_free(ds->value); + + free(d); +} + +static void data_string_reset(data_unset *d) { + data_string *ds = (data_string *)d; + + /* reused array elements */ + buffer_reset(ds->key); + buffer_reset(ds->value); +} + +static int data_string_insert_dup(data_unset *dst, data_unset *src) { + data_string *ds_dst = (data_string *)dst; + data_string *ds_src = (data_string *)src; + + if (ds_dst->value->used) { + buffer_append_string(ds_dst->value, ", "); + buffer_append_string_buffer(ds_dst->value, ds_src->value); + } else { + buffer_copy_string_buffer(ds_dst->value, ds_src->value); + } + + src->free(src); + + return 0; +} + +static int data_response_insert_dup(data_unset *dst, data_unset *src) { + data_string *ds_dst = (data_string *)dst; + data_string *ds_src = (data_string *)src; + + if (ds_dst->value->used) { + buffer_append_string(ds_dst->value, "\r\n"); + buffer_append_string_buffer(ds_dst->value, ds_dst->key); + buffer_append_string(ds_dst->value, ": "); + buffer_append_string_buffer(ds_dst->value, ds_src->value); + } else { + buffer_copy_string_buffer(ds_dst->value, ds_src->value); + } + + src->free(src); + + return 0; +} + + +static void data_string_print(data_unset *d) { + data_string *ds = (data_string *)d; + + fprintf(stderr, "{%s: %s}", ds->key->ptr, ds->value->used ? ds->value->ptr : ""); +} + + +data_string *data_string_init(void) { + data_string *ds; + + ds = calloc(1, sizeof(*ds)); + assert(ds); + + ds->key = buffer_init(); + ds->value = buffer_init(); + + ds->free = data_string_free; + ds->reset = data_string_reset; + ds->insert_dup = data_string_insert_dup; + ds->print = data_string_print; + ds->type = TYPE_STRING; + + return ds; +} + +data_string *data_response_init(void) { + data_string *ds; + + ds = data_string_init(); + ds->insert_dup = data_response_insert_dup; + + return ds; +} diff --git a/src/etag.c b/src/etag.c new file mode 100644 index 00000000..eb1b2f41 --- /dev/null +++ b/src/etag.c @@ -0,0 +1,30 @@ +#include <string.h> + +#include "buffer.h" +#include "etag.h" + +int etag_is_equal(buffer *etag, const char *matches) { + if (0 == strcmp(etag->ptr, matches)) return 1; + return 0; +} + +int etag_create(buffer *etag, struct stat *st) { + buffer_copy_off_t(etag, st->st_ino); + buffer_append_string_len(etag, "-", 1); + buffer_append_off_t(etag, st->st_size); + buffer_append_string_len(etag, "-", 1); + buffer_append_long(etag, st->st_mtime); + + return 0; +} + +int etag_mutate(buffer *mut, buffer *etag) { + size_t h, i; + + for (h=0, i=0; i < etag->used; ++i) h = (h<<5)^(h>>27)^(etag->ptr[i]); + + buffer_reset(mut); + buffer_copy_long(mut, h); + + return 0; +} diff --git a/src/etag.h b/src/etag.h new file mode 100644 index 00000000..53fae00a --- /dev/null +++ b/src/etag.h @@ -0,0 +1,15 @@ +#ifndef ETAG_H +#define ETAG_H + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "buffer.h" + +int etag_is_equal(buffer *etag, const char *matches); +int etag_create(buffer *etag, struct stat *st); +int etag_mutate(buffer *mut, buffer *etag); + + +#endif diff --git a/src/fastcgi.h b/src/fastcgi.h new file mode 100644 index 00000000..15f1deae --- /dev/null +++ b/src/fastcgi.h @@ -0,0 +1,136 @@ +/* + * fastcgi.h -- + * + * Defines for the FastCGI protocol. + * + * + * Copyright (c) 1995-1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: fastcgi.h,v 1.1.1.1 2003/10/18 09:54:10 weigon Exp $ + */ + +#ifndef _FASTCGI_H +#define _FASTCGI_H + +/* + * Listening socket file number + */ +#define FCGI_LISTENSOCK_FILENO 0 + +typedef struct { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} FCGI_Header; + +#define FCGI_MAX_LENGTH 0xffff + +/* + * Number of bytes in a FCGI_Header. Future versions of the protocol + * will not reduce this number. + */ +#define FCGI_HEADER_LEN 8 + +/* + * Value for version component of FCGI_Header + */ +#define FCGI_VERSION_1 1 + +/* + * Values for type component of FCGI_Header + */ +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +/* + * Value for requestId component of FCGI_Header + */ +#define FCGI_NULL_REQUEST_ID 0 + + +typedef struct { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} FCGI_BeginRequestBody; + +typedef struct { + FCGI_Header header; + FCGI_BeginRequestBody body; +} FCGI_BeginRequestRecord; + +/* + * Mask for flags component of FCGI_BeginRequestBody + */ +#define FCGI_KEEP_CONN 1 + +/* + * Values for role component of FCGI_BeginRequestBody + */ +#define FCGI_RESPONDER 1 +#define FCGI_AUTHORIZER 2 +#define FCGI_FILTER 3 + + +typedef struct { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} FCGI_EndRequestBody; + +typedef struct { + FCGI_Header header; + FCGI_EndRequestBody body; +} FCGI_EndRequestRecord; + +/* + * Values for protocolStatus component of FCGI_EndRequestBody + */ +#define FCGI_REQUEST_COMPLETE 0 +#define FCGI_CANT_MPX_CONN 1 +#define FCGI_OVERLOADED 2 +#define FCGI_UNKNOWN_ROLE 3 + + +/* + * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records + */ +#define FCGI_MAX_CONNS "FCGI_MAX_CONNS" +#define FCGI_MAX_REQS "FCGI_MAX_REQS" +#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" + + +typedef struct { + unsigned char type; + unsigned char reserved[7]; +} FCGI_UnknownTypeBody; + +typedef struct { + FCGI_Header header; + FCGI_UnknownTypeBody body; +} FCGI_UnknownTypeRecord; + +#endif /* _FASTCGI_H */ + diff --git a/src/fdevent.c b/src/fdevent.c new file mode 100644 index 00000000..7deda395 --- /dev/null +++ b/src/fdevent.c @@ -0,0 +1,202 @@ +#include <sys/types.h> + +#include "settings.h" + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> + +#include "fdevent.h" +#include "buffer.h" + +fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type) { + fdevents *ev; + + ev = calloc(1, sizeof(*ev)); + ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray)); + ev->maxfds = maxfds; + + switch(type) { + case FDEVENT_HANDLER_POLL: + if (0 != fdevent_poll_init(ev)) { + fprintf(stderr, "%s.%d: event-handler poll failed\n", + __FILE__, __LINE__); + + return NULL; + } + break; + case FDEVENT_HANDLER_SELECT: + if (0 != fdevent_select_init(ev)) { + fprintf(stderr, "%s.%d: event-handler select failed\n", + __FILE__, __LINE__); + return NULL; + } + break; + case FDEVENT_HANDLER_LINUX_RTSIG: + if (0 != fdevent_linux_rtsig_init(ev)) { + fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + return NULL; + } + break; + case FDEVENT_HANDLER_LINUX_SYSEPOLL: + if (0 != fdevent_linux_sysepoll_init(ev)) { + fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + return NULL; + } + break; + case FDEVENT_HANDLER_SOLARIS_DEVPOLL: + if (0 != fdevent_solaris_devpoll_init(ev)) { + fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + return NULL; + } + break; + case FDEVENT_HANDLER_FREEBSD_KQUEUE: + if (0 != fdevent_freebsd_kqueue_init(ev)) { + fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + return NULL; + } + break; + default: + fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + return NULL; + } + + return ev; +} + +void fdevent_free(fdevents *ev) { + size_t i; + if (!ev) return; + + if (ev->free) ev->free(ev); + + for (i = 0; i < ev->maxfds; i++) { + if (ev->fdarray[i]) free(ev->fdarray[i]); + } + + free(ev->fdarray); + free(ev); +} + +int fdevent_reset(fdevents *ev) { + if (ev->reset) return ev->reset(ev); + + return 0; +} + +fdnode *fdnode_init() { + fdnode *fdn; + + fdn = calloc(1, sizeof(*fdn)); + fdn->fd = -1; + return fdn; +} + +void fdnode_free(fdnode *fdn) { + free(fdn); +} + +int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx) { + fdnode *fdn; + + fdn = fdnode_init(); + fdn->handler = handler; + fdn->fd = fd; + fdn->ctx = ctx; + + ev->fdarray[fd] = fdn; + + return 0; +} + +int fdevent_unregister(fdevents *ev, int fd) { + fdnode *fdn; + if (!ev) return 0; + fdn = ev->fdarray[fd]; + + fdnode_free(fdn); + + ev->fdarray[fd] = NULL; + + return 0; +} + +int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd) { + int fde = fde_ndx ? *fde_ndx : -1; + + if (ev->event_del) fde = ev->event_del(ev, fde, fd); + + if (fde_ndx) *fde_ndx = fde; + + return 0; +} + +int fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int events) { + int fde = fde_ndx ? *fde_ndx : -1; + + if (ev->event_add) fde = ev->event_add(ev, fde, fd, events); + + if (fde_ndx) *fde_ndx = fde; + + return 0; +} + +int fdevent_poll(fdevents *ev, int timeout_ms) { + if (ev->poll == NULL) SEGFAULT(); + return ev->poll(ev, timeout_ms); +} + +int fdevent_event_get_revent(fdevents *ev, size_t ndx) { + if (ev->event_get_revent == NULL) SEGFAULT(); + + return ev->event_get_revent(ev, ndx); +} + +int fdevent_event_get_fd(fdevents *ev, size_t ndx) { + if (ev->event_get_fd == NULL) SEGFAULT(); + + return ev->event_get_fd(ev, ndx); +} + +fdevent_handler fdevent_get_handler(fdevents *ev, int fd) { + if (ev->fdarray[fd] == NULL) SEGFAULT(); + if (ev->fdarray[fd]->fd != fd) SEGFAULT(); + + return ev->fdarray[fd]->handler; +} + +void * fdevent_get_context(fdevents *ev, int fd) { + if (ev->fdarray[fd] == NULL) SEGFAULT(); + if (ev->fdarray[fd]->fd != fd) SEGFAULT(); + + return ev->fdarray[fd]->ctx; +} + +int fdevent_fcntl_set(fdevents *ev, int fd) { +#ifdef FD_CLOEXEC + /* close fd on exec (cgi) */ + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + if (ev->fcntl_set) return ev->fcntl_set(ev, fd); +#ifdef O_NONBLOCK + return fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); +#else + return 0; +#endif +} + + +int fdevent_event_next_fdndx(fdevents *ev, int ndx) { + if (ev->event_next_fdndx) return ev->event_next_fdndx(ev, ndx); + + return -1; +} + diff --git a/src/fdevent.h b/src/fdevent.h new file mode 100644 index 00000000..5197713b --- /dev/null +++ b/src/fdevent.h @@ -0,0 +1,222 @@ +#ifndef _FDEVENT_H_ +#define _FDEVENT_H_ + +#include "config.h" +#include "settings.h" +#include "bitset.h" + +/* select event-system */ + +#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H) +# if defined HAVE_STDINT_H +# include <stdint.h> +# endif +# define USE_LINUX_EPOLL +# include <sys/epoll.h> +#endif + +/* MacOS 10.3.x has poll.h under /usr/include/, all other unixes + * under /usr/include/sys/ */ +#if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H)) +# define USE_POLL +# ifdef HAVE_POLL_H +# include <poll.h> +# else +# include <sys/poll.h> +# endif +# if defined HAVE_SIGTIMEDWAIT && defined(__linux__) +# define USE_LINUX_SIGIO +# include <signal.h> +# endif +#endif + +#if defined HAVE_SELECT +# ifdef __WIN32 +# include <winsock2.h> +# endif +# define USE_SELECT +#endif + +#if defined HAVE_SYS_DEVPOLL_H && defined(__sun) +# define USE_SOLARIS_DEVPOLL +# include <sys/devpoll.h> +#endif + +#if defined HAVE_SYS_EVENT_H && defined HAVE_KQUEUE +# define USE_FREEBSD_KQUEUE +# include <sys/event.h> +#endif + +#if defined HAVE_SYS_PORT_H && defined HAVE_PORT_CREATE +# define USE_SOLARIS_PORT +# include <sys/port.h> +#endif + + +typedef handler_t (*fdevent_handler)(void *srv, void *ctx, int revents); + +#define FDEVENT_IN BV(0) +#define FDEVENT_PRI BV(1) +#define FDEVENT_OUT BV(2) +#define FDEVENT_ERR BV(3) +#define FDEVENT_HUP BV(4) +#define FDEVENT_NVAL BV(5) + +typedef enum { FD_EVENT_TYPE_UNSET = -1, + FD_EVENT_TYPE_CONNECTION, + FD_EVENT_TYPE_FCGI_CONNECTION, + FD_EVENT_TYPE_DIRWATCH, + FD_EVENT_TYPE_CGI_CONNECTION +} fd_event_t; + +typedef enum { FDEVENT_HANDLER_UNSET, + FDEVENT_HANDLER_SELECT, + FDEVENT_HANDLER_POLL, + FDEVENT_HANDLER_LINUX_RTSIG, + FDEVENT_HANDLER_LINUX_SYSEPOLL, + FDEVENT_HANDLER_SOLARIS_DEVPOLL, + FDEVENT_HANDLER_FREEBSD_KQUEUE, + FDEVENT_HANDLER_SOLARIS_PORT +} fdevent_handler_t; + +/** + * a mapping from fd to connection structure + * + */ +typedef struct { + int fd; /**< the fd */ + void *conn; /**< a reference the corresponding data-structure */ + fd_event_t fd_type; /**< type of the fd */ + int events; /**< registered events */ + int revents; +} fd_conn; + +typedef struct { + fd_conn *ptr; + + size_t size; + size_t used; +} fd_conn_buffer; + +/** + * array of unused fd's + * + */ + +typedef struct fdnode { + fdevent_handler handler; + void *ctx; + int fd; + + struct fdnode *prev, *next; +} fdnode; + +typedef struct { + fdnode *first, *last; +} fdlist; + +typedef struct { + int *ptr; + + size_t used; + size_t size; +} buffer_int; + +/** + * fd-event handler for select(), poll() and rt-signals on Linux 2.4 + * + */ +typedef struct fdevents { + fdevent_handler_t type; + + fdlist fdlist; + fdnode **fdarray; + size_t maxfds; + +#ifdef USE_LINUX_SIGIO + int in_sigio; + int signum; + sigset_t sigset; + siginfo_t siginfo; + bitset *sigbset; +#endif +#ifdef USE_LINUX_EPOLL + int epoll_fd; + struct epoll_event *epoll_events; +#endif +#ifdef USE_POLL + struct pollfd *pollfds; + + size_t size; + size_t used; + + buffer_int unused; +#endif +#ifdef USE_SELECT + fd_set select_read; + fd_set select_write; + fd_set select_error; + + fd_set select_set_read; + fd_set select_set_write; + fd_set select_set_error; + + int select_max_fd; +#endif +#ifdef USE_SOLARIS_DEVPOLL + int devpoll_fd; + struct pollfd *devpollfds; +#endif +#ifdef USE_FREEBSD_KQUEUE + int kq_fd; + struct kevent *kq_results; + bitset *kq_bevents; +#endif +#ifdef USE_SOLARIS_PORT + int port_fd; +#endif + int (*reset)(struct fdevents *ev); + void (*free)(struct fdevents *ev); + + int (*event_add)(struct fdevents *ev, int fde_ndx, int fd, int events); + int (*event_del)(struct fdevents *ev, int fde_ndx, int fd); + int (*event_get_revent)(struct fdevents *ev, size_t ndx); + int (*event_get_fd)(struct fdevents *ev, size_t ndx); + + int (*event_next_fdndx)(struct fdevents *ev, int ndx); + + int (*poll)(struct fdevents *ev, int timeout_ms); + + int (*fcntl_set)(struct fdevents *ev, int fd); +} fdevents; + +fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type); +int fdevent_reset(fdevents *ev); +void fdevent_free(fdevents *ev); + +int fdevent_event_add(fdevents *ev, int *fde_ndx, int fd, int events); +int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd); +int fdevent_event_get_revent(fdevents *ev, size_t ndx); +int fdevent_event_get_fd(fdevents *ev, size_t ndx); +fdevent_handler fdevent_get_handler(fdevents *ev, int fd); +void * fdevent_get_context(fdevents *ev, int fd); + +int fdevent_event_next_fdndx(fdevents *ev, int ndx); + +int fdevent_poll(fdevents *ev, int timeout_ms); + +int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx); +int fdevent_unregister(fdevents *ev, int fd); + +int fdevent_fcntl_set(fdevents *ev, int fd); + +int fdevent_select_init(fdevents *ev); +int fdevent_poll_init(fdevents *ev); +int fdevent_linux_rtsig_init(fdevents *ev); +int fdevent_linux_sysepoll_init(fdevents *ev); +int fdevent_solaris_devpoll_init(fdevents *ev); +int fdevent_freebsd_kqueue_init(fdevents *ev); + +#endif + + diff --git a/src/fdevent_freebsd_kqueue.c b/src/fdevent_freebsd_kqueue.c new file mode 100644 index 00000000..0585cfbf --- /dev/null +++ b/src/fdevent_freebsd_kqueue.c @@ -0,0 +1,194 @@ +#include <sys/types.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> + +#include "fdevent.h" +#include "settings.h" +#include "buffer.h" +#include "server.h" + +#ifdef USE_FREEBSD_KQUEUE +#include <sys/event.h> +#include <sys/time.h> + +static void fdevent_freebsd_kqueue_free(fdevents *ev) { + close(ev->kq_fd); + free(ev->kq_results); + bitset_free(ev->kq_bevents); +} + +static int fdevent_freebsd_kqueue_event_del(fdevents *ev, int fde_ndx, int fd) { + int filter, ret; + struct kevent kev; + struct timespec ts; + + if (fde_ndx < 0) return -1; + + filter = bitset_test_bit(ev->kq_bevents, fd) ? EVFILT_READ : EVFILT_WRITE; + + EV_SET(&kev, fd, filter, EV_DELETE, 0, 0, NULL); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + + ret = kevent(ev->kq_fd, + &kev, 1, + NULL, 0, + &ts); + + if (ret == -1) { + fprintf(stderr, "%s.%d: kqueue failed polling: %s\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + + return -1; +} + +static int fdevent_freebsd_kqueue_event_add(fdevents *ev, int fde_ndx, int fd, int events) { + int filter, ret; + struct kevent kev; + struct timespec ts; + + UNUSED(fde_ndx); + + filter = (events & FDEVENT_IN) ? EVFILT_READ : EVFILT_WRITE; + + EV_SET(&kev, fd, filter, EV_ADD|EV_CLEAR, 0, 0, NULL); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + + ret = kevent(ev->kq_fd, + &kev, 1, + NULL, 0, + &ts); + + if (ret == -1) { + fprintf(stderr, "%s.%d: kqueue failed polling: %s\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + + if (filter == EVFILT_READ) { + bitset_set_bit(ev->kq_bevents, fd); + } else { + bitset_clear_bit(ev->kq_bevents, fd); + } + + return fd; +} + +static int fdevent_freebsd_kqueue_poll(fdevents *ev, int timeout_ms) { + int ret; + struct timespec ts; + + ts.tv_sec = timeout_ms / 1000; + ts.tv_nsec = (timeout_ms % 1000) * 1000000; + + ret = kevent(ev->kq_fd, + NULL, 0, + ev->kq_results, ev->maxfds, + &ts); + + if (ret == -1) { + fprintf(stderr, "%s.%d: kqueue failed polling: %s\n", + __FILE__, __LINE__, strerror(errno)); + } + + return ret; +} + +static int fdevent_freebsd_kqueue_event_get_revent(fdevents *ev, size_t ndx) { + int events = 0, e; + + e = ev->kq_results[ndx].filter; + + if (e == EVFILT_READ) { + events |= FDEVENT_IN; + } else if (e == EVFILT_WRITE) { + events |= FDEVENT_OUT; + } + + e = ev->kq_results[ndx].flags; + + if (e & EV_EOF) { + events |= FDEVENT_HUP; + } + + if (e & EV_ERROR) { + events |= FDEVENT_ERR; + } + + return events; +} + +static int fdevent_freebsd_kqueue_event_get_fd(fdevents *ev, size_t ndx) { + return ev->kq_results[ndx].ident; +} + +static int fdevent_freebsd_kqueue_event_next_fdndx(fdevents *ev, int ndx) { + UNUSED(ev); + + return (ndx < 0) ? 0 : ndx + 1; +} + +static int fdevent_freebsd_kqueue_reset(fdevents *ev) { + close(ev->kq_fd); + + if (-1 == (ev->kq_fd = kqueue())) { + fprintf(stderr, "%s.%d: kqueue failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + + return 0; +} + + +int fdevent_freebsd_kqueue_init(fdevents *ev) { + ev->type = FDEVENT_HANDLER_FREEBSD_KQUEUE; +#define SET(x) \ + ev->x = fdevent_freebsd_kqueue_##x; + + SET(free); + SET(poll); + SET(reset); + + SET(event_del); + SET(event_add); + + SET(event_next_fdndx); + SET(event_get_fd); + SET(event_get_revent); + + if (-1 == (ev->kq_fd = kqueue())) { + fprintf(stderr, "%s.%d: kqueue failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + ev->kq_results = calloc(ev->maxfds, sizeof(*ev->kq_results)); + ev->kq_bevents = bitset_init(ev->maxfds); + + return 0; +} +#else +int fdevent_freebsd_kqueue_init(fdevents *ev) { + UNUSED(ev); + + fprintf(stderr, "%s.%d: kqueue not available, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + + return -1; +} +#endif diff --git a/src/fdevent_linux_rtsig.c b/src/fdevent_linux_rtsig.c new file mode 100644 index 00000000..8bedaf0d --- /dev/null +++ b/src/fdevent_linux_rtsig.c @@ -0,0 +1,260 @@ +#include <sys/types.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <limits.h> + +#define __USE_GNU +#include <fcntl.h> + +#include "fdevent.h" +#include "settings.h" +#include "buffer.h" + +#ifdef USE_LINUX_SIGIO +static void fdevent_linux_rtsig_free(fdevents *ev) { + free(ev->pollfds); + if (ev->unused.ptr) free(ev->unused.ptr); + + bitset_free(ev->sigbset); +} + + +static int fdevent_linux_rtsig_event_del(fdevents *ev, int fde_ndx, int fd) { + if (fde_ndx < 0) return -1; + + if ((size_t)fde_ndx >= ev->used) { + fprintf(stderr, "%s.%d: del! out of range %d %u\n", __FILE__, __LINE__, fde_ndx, ev->used); + SEGFAULT(); + } + + if (ev->pollfds[fde_ndx].fd == fd) { + size_t k = fde_ndx; + + ev->pollfds[k].fd = -1; + + bitset_clear_bit(ev->sigbset, fd); + + if (ev->unused.size == 0) { + ev->unused.size = 16; + ev->unused.ptr = malloc(sizeof(*(ev->unused.ptr)) * ev->unused.size); + } else if (ev->unused.size == ev->unused.used) { + ev->unused.size += 16; + ev->unused.ptr = realloc(ev->unused.ptr, sizeof(*(ev->unused.ptr)) * ev->unused.size); + } + + ev->unused.ptr[ev->unused.used++] = k; + } else { + fprintf(stderr, "%s.%d: del! %d %d\n", __FILE__, __LINE__, ev->pollfds[fde_ndx].fd, fd); + + SEGFAULT(); + } + + return -1; +} + +#if 0 +static int fdevent_linux_rtsig_event_compress(fdevents *ev) { + size_t j; + + if (ev->used == 0) return 0; + if (ev->unused.used != 0) return 0; + + for (j = ev->used - 1; j + 1 > 0; j--) { + if (ev->pollfds[j].fd == -1) ev->used--; + } + + + return 0; +} +#endif + +static int fdevent_linux_rtsig_event_add(fdevents *ev, int fde_ndx, int fd, int events) { + /* known index */ + if (fde_ndx != -1) { + if (ev->pollfds[fde_ndx].fd == fd) { + ev->pollfds[fde_ndx].events = events; + + return fde_ndx; + } + fprintf(stderr, "%s.%d: add: (%d, %d)\n", __FILE__, __LINE__, fde_ndx, ev->pollfds[fde_ndx].fd); + SEGFAULT(); + } + + if (ev->unused.used > 0) { + int k = ev->unused.ptr[--ev->unused.used]; + + ev->pollfds[k].fd = fd; + ev->pollfds[k].events = events; + + bitset_set_bit(ev->sigbset, fd); + + return k; + } else { + if (ev->size == 0) { + ev->size = 16; + ev->pollfds = malloc(sizeof(*ev->pollfds) * ev->size); + } else if (ev->size == ev->used) { + ev->size += 16; + ev->pollfds = realloc(ev->pollfds, sizeof(*ev->pollfds) * ev->size); + } + + ev->pollfds[ev->used].fd = fd; + ev->pollfds[ev->used].events = events; + + bitset_set_bit(ev->sigbset, fd); + + return ev->used++; + } +} + +static int fdevent_linux_rtsig_poll(fdevents *ev, int timeout_ms) { + struct timespec ts; + int r; + +#if 0 + fdevent_linux_rtsig_event_compress(ev); +#endif + + ev->in_sigio = 1; + + ts.tv_sec = timeout_ms / 1000; + ts.tv_nsec = (timeout_ms % 1000) * 1000000; + r = sigtimedwait(&(ev->sigset), &(ev->siginfo), &(ts)); + + if (r == -1) { + if (errno == EAGAIN) return 0; + return r; + } else if (r == SIGIO) { + struct sigaction act; + + /* flush the signal queue */ + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_IGN; + sigaction(ev->signum, &act, NULL); + + /* re-enable the signal queue */ + act.sa_handler = SIG_DFL; + sigaction(ev->signum, &act, NULL); + + ev->in_sigio = 0; + r = poll(ev->pollfds, ev->used, timeout_ms); + + return r; + } else if (r == ev->signum) { +# if 0 + fprintf(stderr, "event: %d %02lx\n", ev->siginfo.si_fd, ev->siginfo.si_band); +# endif + return bitset_test_bit(ev->sigbset, ev->siginfo.si_fd); + } else { + /* ? */ + return -1; + } +} + +static int fdevent_linux_rtsig_event_get_revent(fdevents *ev, size_t ndx) { + if (ev->in_sigio == 1) { +# if 0 + if (ev->siginfo.si_band == POLLERR) { + fprintf(stderr, "event: %d %02lx %02x %s\n", ev->siginfo.si_fd, ev->siginfo.si_band, errno, strerror(errno)); + } +# endif + if (ndx != 0) { + fprintf(stderr, "+\n"); + return 0; + } + + return ev->siginfo.si_band & 0x3f; + } else { + if (ndx >= ev->used) { + fprintf(stderr, "%s.%d: event: %u %u\n", __FILE__, __LINE__, ndx, ev->used); + return 0; + } + return ev->pollfds[ndx].revents; + } +} + +static int fdevent_linux_rtsig_event_get_fd(fdevents *ev, size_t ndx) { + if (ev->in_sigio == 1) { + return ev->siginfo.si_fd; + } else { + return ev->pollfds[ndx].fd; + } +} + +static int fdevent_linux_rtsig_fcntl_set(fdevents *ev, int fd) { + static pid_t pid = 0; + + if (pid == 0) pid = getpid(); + + if (-1 == fcntl(fd, F_SETSIG, ev->signum)) return -1; + + if (-1 == fcntl(fd, F_SETOWN, (int) pid)) return -1; + + return fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK | O_RDWR); +} + + +static int fdevent_linux_rtsig_event_next_fdndx(fdevents *ev, int ndx) { + if (ev->in_sigio == 1) { + if (ndx < 0) return 0; + return -1; + } else { + size_t i; + + i = (ndx < 0) ? 0 : ndx + 1; + for (; i < ev->used; i++) { + if (ev->pollfds[i].revents) break; + } + + return i; + } +} + +int fdevent_linux_rtsig_init(fdevents *ev) { + ev->type = FDEVENT_HANDLER_LINUX_RTSIG; +#define SET(x) \ + ev->x = fdevent_linux_rtsig_##x; + + SET(free); + SET(poll); + + SET(event_del); + SET(event_add); + + SET(event_next_fdndx); + SET(fcntl_set); + SET(event_get_fd); + SET(event_get_revent); + + ev->signum = SIGRTMIN + 1; + + sigemptyset(&(ev->sigset)); + sigaddset(&(ev->sigset), ev->signum); + sigaddset(&(ev->sigset), SIGIO); + if (-1 == sigprocmask(SIG_BLOCK, &(ev->sigset), NULL)) { + fprintf(stderr, "%s.%d: sigprocmask failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + + ev->in_sigio = 1; + + ev->sigbset = bitset_init(ev->maxfds); + + return 0; +} +#else +int fdevent_linux_rtsig_init(fdevents *ev) { + UNUSED(ev); + + fprintf(stderr, "%s.%d: linux-rtsig not supported, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + return -1; +} +#endif diff --git a/src/fdevent_linux_sysepoll.c b/src/fdevent_linux_sysepoll.c new file mode 100644 index 00000000..4175fc65 --- /dev/null +++ b/src/fdevent_linux_sysepoll.c @@ -0,0 +1,152 @@ +#include <sys/types.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> + +#include "fdevent.h" +#include "settings.h" +#include "buffer.h" + +#ifdef USE_LINUX_EPOLL +static void fdevent_linux_sysepoll_free(fdevents *ev) { + close(ev->epoll_fd); + free(ev->epoll_events); +} + +static int fdevent_linux_sysepoll_event_del(fdevents *ev, int fde_ndx, int fd) { + struct epoll_event ep; + + if (fde_ndx < 0) return -1; + + memset(&ep, 0, sizeof(ep)); + + ep.data.fd = fd; + ep.data.ptr = NULL; + + if (0 != epoll_ctl(ev->epoll_fd, EPOLL_CTL_DEL, fd, &ep)) { + fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno)); + + SEGFAULT(); + + return 0; + } + + + return -1; +} + +static int fdevent_linux_sysepoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) { + struct epoll_event ep; + int add = 0; + + if (fde_ndx == -1) add = 1; + + memset(&ep, 0, sizeof(ep)); + + ep.events = 0; + + if (events & FDEVENT_IN) ep.events |= EPOLLIN; + if (events & FDEVENT_OUT) ep.events |= EPOLLOUT; + + ep.events |= EPOLLERR | EPOLLHUP | EPOLLET; + + ep.data.ptr = NULL; + ep.data.fd = fd; + + if (0 != epoll_ctl(ev->epoll_fd, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ep)) { + fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno)); + + SEGFAULT(); + + return 0; + } + + return fd; +} + +static int fdevent_linux_sysepoll_poll(fdevents *ev, int timeout_ms) { + return epoll_wait(ev->epoll_fd, ev->epoll_events, ev->maxfds, timeout_ms); +} + +static int fdevent_linux_sysepoll_event_get_revent(fdevents *ev, size_t ndx) { + int events = 0, e; + + e = ev->epoll_events[ndx].events; + if (e & EPOLLIN) events |= FDEVENT_IN; + if (e & EPOLLOUT) events |= FDEVENT_OUT; + if (e & EPOLLERR) events |= FDEVENT_ERR; + if (e & EPOLLHUP) events |= FDEVENT_HUP; + if (e & EPOLLPRI) events |= FDEVENT_PRI; + + return e; +} + +static int fdevent_linux_sysepoll_event_get_fd(fdevents *ev, size_t ndx) { +# if 0 + fprintf(stderr, "%s.%d: %d, %d\n", __FILE__, __LINE__, ndx, ev->epoll_events[ndx].data.fd); +# endif + + return ev->epoll_events[ndx].data.fd; +} + +static int fdevent_linux_sysepoll_event_next_fdndx(fdevents *ev, int ndx) { + size_t i; + + UNUSED(ev); + + i = (ndx < 0) ? 0 : ndx + 1; + + return i; +} + +int fdevent_linux_sysepoll_init(fdevents *ev) { + ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL; +#define SET(x) \ + ev->x = fdevent_linux_sysepoll_##x; + + SET(free); + SET(poll); + + SET(event_del); + SET(event_add); + + SET(event_next_fdndx); + SET(event_get_fd); + SET(event_get_revent); + + if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds))) { + fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + + if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC)) { + fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + close(ev->epoll_fd); + + return -1; + } + + ev->epoll_events = malloc(ev->maxfds * sizeof(*ev->epoll_events)); + + return 0; +} + +#else +int fdevent_linux_sysepoll_init(fdevents *ev) { + UNUSED(ev); + + fprintf(stderr, "%s.%d: linux-sysepoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + + return -1; +} +#endif diff --git a/src/fdevent_poll.c b/src/fdevent_poll.c new file mode 100644 index 00000000..07a67a2f --- /dev/null +++ b/src/fdevent_poll.c @@ -0,0 +1,182 @@ +#include <sys/types.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> + +#include "fdevent.h" +#include "settings.h" +#include "buffer.h" + +#ifdef USE_POLL +static void fdevent_poll_free(fdevents *ev) { + free(ev->pollfds); + if (ev->unused.ptr) free(ev->unused.ptr); +} + +static int fdevent_poll_event_del(fdevents *ev, int fde_ndx, int fd) { + if (fde_ndx < 0) return -1; + + if ((size_t)fde_ndx >= ev->used) { + fprintf(stderr, "%s.%d: del! out of range %d %u\n", __FILE__, __LINE__, fde_ndx, ev->used); + SEGFAULT(); + } + + if (ev->pollfds[fde_ndx].fd == fd) { + size_t k = fde_ndx; + + ev->pollfds[k].fd = -1; + /* ev->pollfds[k].events = 0; */ + /* ev->pollfds[k].revents = 0; */ + + if (ev->unused.size == 0) { + ev->unused.size = 16; + ev->unused.ptr = malloc(sizeof(*(ev->unused.ptr)) * ev->unused.size); + } else if (ev->unused.size == ev->unused.used) { + ev->unused.size += 16; + ev->unused.ptr = realloc(ev->unused.ptr, sizeof(*(ev->unused.ptr)) * ev->unused.size); + } + + ev->unused.ptr[ev->unused.used++] = k; + } else { + SEGFAULT(); + } + + return -1; +} + +#if 0 +static int fdevent_poll_event_compress(fdevents *ev) { + size_t j; + + if (ev->used == 0) return 0; + if (ev->unused.used != 0) return 0; + + for (j = ev->used - 1; j + 1 > 0 && ev->pollfds[j].fd == -1; j--) ev->used--; + + return 0; +} +#endif + +static int fdevent_poll_event_add(fdevents *ev, int fde_ndx, int fd, int events) { + /* known index */ + + if (fde_ndx != -1) { + if (ev->pollfds[fde_ndx].fd == fd) { + ev->pollfds[fde_ndx].events = events; + + return fde_ndx; + } + fprintf(stderr, "%s.%d: add: (%d, %d)\n", __FILE__, __LINE__, fde_ndx, ev->pollfds[fde_ndx].fd); + SEGFAULT(); + } + + if (ev->unused.used > 0) { + int k = ev->unused.ptr[--ev->unused.used]; + + ev->pollfds[k].fd = fd; + ev->pollfds[k].events = events; + + return k; + } else { + if (ev->size == 0) { + ev->size = 16; + ev->pollfds = malloc(sizeof(*ev->pollfds) * ev->size); + } else if (ev->size == ev->used) { + ev->size += 16; + ev->pollfds = realloc(ev->pollfds, sizeof(*ev->pollfds) * ev->size); + } + + ev->pollfds[ev->used].fd = fd; + ev->pollfds[ev->used].events = events; + + return ev->used++; + } +} + +static int fdevent_poll_poll(fdevents *ev, int timeout_ms) { +#if 0 + fdevent_poll_event_compress(ev); +#endif + return poll(ev->pollfds, ev->used, timeout_ms); +} + +static int fdevent_poll_event_get_revent(fdevents *ev, size_t ndx) { + int r, poll_r; + if (ndx >= ev->used) { + fprintf(stderr, "%s.%d: dying because: event: %u >= %u\n", __FILE__, __LINE__, ndx, ev->used); + + SEGFAULT(); + + return 0; + } + + if (ev->pollfds[ndx].revents & POLLNVAL) { + /* should never happen */ + SEGFAULT(); + } + + r = 0; + poll_r = ev->pollfds[ndx].revents; + + /* map POLL* to FDEVEN_* */ + + if (poll_r & POLLIN) r |= FDEVENT_IN; + if (poll_r & POLLOUT) r |= FDEVENT_OUT; + if (poll_r & POLLERR) r |= FDEVENT_ERR; + if (poll_r & POLLHUP) r |= FDEVENT_HUP; + if (poll_r & POLLNVAL) r |= FDEVENT_NVAL; + if (poll_r & POLLPRI) r |= FDEVENT_PRI; + + return ev->pollfds[ndx].revents; +} + +static int fdevent_poll_event_get_fd(fdevents *ev, size_t ndx) { + return ev->pollfds[ndx].fd; +} + +static int fdevent_poll_event_next_fdndx(fdevents *ev, int ndx) { + size_t i; + + i = (ndx < 0) ? 0 : ndx + 1; + for (; i < ev->used; i++) { + if (ev->pollfds[i].revents) break; + } + + return i; +} + +int fdevent_poll_init(fdevents *ev) { + ev->type = FDEVENT_HANDLER_POLL; +#define SET(x) \ + ev->x = fdevent_poll_##x; + + SET(free); + SET(poll); + + SET(event_del); + SET(event_add); + + SET(event_next_fdndx); + SET(event_get_fd); + SET(event_get_revent); + + return 0; +} + + + + +#else +int fdevent_poll_init(fdevents *ev) { + UNUSED(ev); + + fprintf(stderr, "%s.%d: poll is not supported, try to set server.event-handler = \"select\"\n", + __FILE__, __LINE__); + return -1; +} +#endif diff --git a/src/fdevent_select.c b/src/fdevent_select.c new file mode 100644 index 00000000..fe9d7236 --- /dev/null +++ b/src/fdevent_select.c @@ -0,0 +1,127 @@ +#include <sys/time.h> +#include <sys/types.h> + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> + +#include "fdevent.h" +#include "settings.h" +#include "buffer.h" + +#ifdef USE_SELECT + +static int fdevent_select_reset(fdevents *ev) { + FD_ZERO(&(ev->select_set_read)); + FD_ZERO(&(ev->select_set_write)); + FD_ZERO(&(ev->select_set_error)); + ev->select_max_fd = -1; + + return 0; +} + +static int fdevent_select_event_del(fdevents *ev, int fde_ndx, int fd) { + if (fde_ndx < 0) return -1; + + FD_CLR(fd, &(ev->select_set_read)); + FD_CLR(fd, &(ev->select_set_write)); + FD_CLR(fd, &(ev->select_set_error)); + + return -1; +} + +static int fdevent_select_event_add(fdevents *ev, int fde_ndx, int fd, int events) { + UNUSED(fde_ndx); + + if (events & FDEVENT_IN) { + FD_SET(fd, &(ev->select_set_read)); + FD_CLR(fd, &(ev->select_set_write)); + } + if (events & FDEVENT_OUT) { + FD_CLR(fd, &(ev->select_set_read)); + FD_SET(fd, &(ev->select_set_write)); + } + FD_SET(fd, &(ev->select_set_error)); + + if (fd > ev->select_max_fd) ev->select_max_fd = fd; + + return fd; +} + +static int fdevent_select_poll(fdevents *ev, int timeout_ms) { + struct timeval tv; + + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + ev->select_read = ev->select_set_read; + ev->select_write = ev->select_set_write; + ev->select_error = ev->select_set_error; + + return select(ev->select_max_fd + 1, &(ev->select_read), &(ev->select_write), &(ev->select_error), &tv); +} + +static int fdevent_select_event_get_revent(fdevents *ev, size_t ndx) { + int revents = 0; + + if (FD_ISSET(ndx, &(ev->select_read))) { + revents |= FDEVENT_IN; + } + if (FD_ISSET(ndx, &(ev->select_write))) { + revents |= FDEVENT_OUT; + } + if (FD_ISSET(ndx, &(ev->select_error))) { + revents |= FDEVENT_ERR; + } + + return revents; +} + +static int fdevent_select_event_get_fd(fdevents *ev, size_t ndx) { + UNUSED(ev); + + return ndx; +} + +static int fdevent_select_event_next_fdndx(fdevents *ev, int ndx) { + int i; + + i = (ndx < 0) ? 0 : ndx + 1; + + for (; i < ev->select_max_fd + 1; i++) { + if (FD_ISSET(i, &(ev->select_read))) break; + if (FD_ISSET(i, &(ev->select_write))) break; + if (FD_ISSET(i, &(ev->select_error))) break; + } + + return i; +} + +int fdevent_select_init(fdevents *ev) { + ev->type = FDEVENT_HANDLER_SELECT; +#define SET(x) \ + ev->x = fdevent_select_##x; + + SET(reset); + SET(poll); + + SET(event_del); + SET(event_add); + + SET(event_next_fdndx); + SET(event_get_fd); + SET(event_get_revent); + + return 0; +} + +#else +int fdevent_select_init(fdevents *ev) { + UNUSED(ev); + + return -1; +} +#endif diff --git a/src/fdevent_solaris_devpoll.c b/src/fdevent_solaris_devpoll.c new file mode 100644 index 00000000..7e6680c1 --- /dev/null +++ b/src/fdevent_solaris_devpoll.c @@ -0,0 +1,141 @@ +#include <sys/types.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> + +#include "fdevent.h" +#include "settings.h" +#include "buffer.h" + +#ifdef USE_SOLARIS_DEVPOLL + +static void fdevent_solaris_devpoll_free(fdevents *ev) { + free(ev->devpollfds); + close(ev->devpoll_fd); +} + +/* return -1 is fine here */ + +static int fdevent_solaris_devpoll_event_del(fdevents *ev, int fde_ndx, int fd) { + struct pollfd pfd; + + if (fde_ndx < 0) return -1; + + pfd.fd = fd; + pfd.events = POLLREMOVE; + pfd.revents = 0; + + if (-1 == write(ev->devpoll_fd, &pfd, sizeof(pfd))) { + fprintf(stderr, "%s.%d: (del) write failed: (%d, %s)\n", + __FILE__, __LINE__, + fd, strerror(errno)); + + return -1; + } + + return -1; +} + +static int fdevent_solaris_devpoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) { + struct pollfd pfd; + int add = 0; + + if (fde_ndx == -1) add = 1; + + pfd.fd = fd; + pfd.events = events; + pfd.revents = 0; + + if (-1 == write(ev->devpoll_fd, &pfd, sizeof(pfd))) { + fprintf(stderr, "%s.%d: (del) write failed: (%d, %s)\n", + __FILE__, __LINE__, + fd, strerror(errno)); + + return -1; + } + + return fd; +} + +static int fdevent_solaris_devpoll_poll(fdevents *ev, int timeout_ms) { + struct dvpoll dopoll; + int ret; + + dopoll.dp_timeout = timeout_ms; + dopoll.dp_nfds = ev->maxfds; + dopoll.dp_fds = ev->devpollfds; + + ret = ioctl(ev->devpoll_fd, DP_POLL, &dopoll); + + return ret; +} + +static int fdevent_solaris_devpoll_event_get_revent(fdevents *ev, size_t ndx) { + return ev->devpollfds[ndx].revents; +} + +static int fdevent_solaris_devpoll_event_get_fd(fdevents *ev, size_t ndx) { + return ev->devpollfds[ndx].fd; +} + +static int fdevent_solaris_devpoll_event_next_fdndx(fdevents *ev, int last_ndx) { + size_t i; + + UNUSED(ev); + + i = (last_ndx < 0) ? 0 : last_ndx + 1; + + return i; +} + +int fdevent_solaris_devpoll_init(fdevents *ev) { + ev->type = FDEVENT_HANDLER_SOLARIS_DEVPOLL; +#define SET(x) \ + ev->x = fdevent_solaris_devpoll_##x; + + SET(free); + SET(poll); + + SET(event_del); + SET(event_add); + + SET(event_next_fdndx); + SET(event_get_fd); + SET(event_get_revent); + + ev->devpollfds = malloc(sizeof(*ev->devpollfds) * ev->maxfds); + + if ((ev->devpoll_fd = open("/dev/poll", O_RDWR)) < 0) { + fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + return -1; + } + + if (fcntl(ev->devpoll_fd, F_SETFD, FD_CLOEXEC) < 0) { + fprintf(stderr, "%s.%d: opening /dev/poll failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__, strerror(errno)); + + close(ev->devpoll_fd); + + return -1; + } + + return 0; +} + +#else +int fdevent_solaris_devpoll_init(fdevents *ev) { + UNUSED(ev); + + fprintf(stderr, "%s.%d: solaris-devpoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n", + __FILE__, __LINE__); + + return -1; +} +#endif diff --git a/src/file_cache.c b/src/file_cache.c new file mode 100644 index 00000000..ee16c77a --- /dev/null +++ b/src/file_cache.c @@ -0,0 +1,483 @@ +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> + +#include "log.h" +#include "file_cache.h" +#include "fdevent.h" +#include "etag.h" + +#ifdef HAVE_ATTR_ATTRIBUTES_H +#include <attr/attributes.h> +#endif + +#include "sys-mmap.h" + +/* NetBSD 1.3.x needs it */ +#ifndef MAP_FAILED +# define MAP_FAILED -1 +#endif + +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif + +#ifndef HAVE_LSTAT +#define lstat stat +#endif + +/* don't enable the dir-cache + * + * F_NOTIFY would be nice but only works with linux-rtsig + */ +#undef USE_LINUX_SIGIO + +file_cache *file_cache_init(void) { + file_cache *fc = NULL; + + fc = calloc(1, sizeof(*fc)); + + fc->dir_name = buffer_init(); + + return fc; +} + +static file_cache_entry * file_cache_entry_init(void) { + file_cache_entry *fce = NULL; + + fce = calloc(1, sizeof(*fce)); + + fce->fd = -1; + fce->fde_ndx = -1; + fce->name = buffer_init(); + fce->etag = buffer_init(); + fce->content_type = buffer_init(); + + return fce; +} + +static void file_cache_entry_free(server *srv, file_cache_entry *fce) { + if (!fce) return; + + if (fce->fd >= 0) { + close(fce->fd); + srv->cur_fds--; + } + + buffer_free(fce->etag); + buffer_free(fce->name); + buffer_free(fce->content_type); + + if (fce->mmap_p) munmap(fce->mmap_p, fce->mmap_length); + + free(fce); +} + +static int file_cache_entry_reset(server *srv, file_cache_entry *fce) { + if (fce->fd < 0) return 0; + + close(fce->fd); + srv->cur_fds--; + +#ifdef USE_LINUX_SIGIO + /* doesn't work anymore */ + if (fce->fde_ndx != -1) { + fdevent_event_del(srv->ev, &(fce->fde_ndx), fce->fd); + } +#else + UNUSED(srv); +#endif + + if (fce->mmap_p) { + munmap(fce->mmap_p, fce->mmap_length); + fce->mmap_p = NULL; + } + fce->fd = -1; + + buffer_reset(fce->etag); + buffer_reset(fce->name); + buffer_reset(fce->content_type); + + return 0; +} + +void file_cache_free(server *srv, file_cache *fc) { + size_t i; + for (i = 0; i < fc->used; i++) { + file_cache_entry_free(srv, fc->ptr[i]); + } + + free(fc->ptr); + + buffer_free(fc->dir_name); + + free(fc); + +} + +#ifdef HAVE_XATTR +int fce_attr_get(buffer *buf, char *name) { + int attrlen; + int ret; + + attrlen = 1024; + buffer_prepare_copy(buf, attrlen); + attrlen--; + if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) { + buf->used = attrlen + 1; + buf->ptr[attrlen] = '\0'; + } + return ret; +} +#endif + +file_cache_entry * file_cache_get_unused_entry(server *srv) { + file_cache_entry *fce = NULL; + file_cache *fc = srv->file_cache; + size_t i; + + if (fc->size == 0) { + fc->size = 16; + fc->ptr = calloc(fc->size, sizeof(*fc->ptr)); + fc->used = 0; + } + for (i = 0; i < fc->used; i++) { + file_cache_entry *f = fc->ptr[i]; + + if (f->fd == -1) { + return f; + } + } + + if (fc->used < fc->size) { + fce = file_cache_entry_init(); + fc->ptr[fc->used++] = fce; + } else { + /* the cache is full, time to resize */ + + fc->size += 16; + fc->ptr = realloc(fc->ptr, sizeof(*fc->ptr) * fc->size); + + fce = file_cache_entry_init(); + fc->ptr[fc->used++] = fce; + } + + return fce; +} + +handler_t file_cache_handle_fdevent(void *_srv, void *_fce, int revent) { + size_t i; + server *srv = _srv; + file_cache_entry *fce = _fce; + file_cache *fc = srv->file_cache;; + + UNUSED(revent); + /* */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sds", "dir has changed: ", fce->fd, fce->name->ptr); +#endif + /* touch all files below this directory */ + + for (i = 0; i < fc->used; i++) { + file_cache_entry *f = fc->ptr[i]; + + if (fce == f) continue; + + if (0 == strncmp(fce->name->ptr, f->name->ptr, fce->name->used - 1)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "ss", "file hit: ", f->name->ptr); +#endif + f->is_dirty = 1; + } + } + + return HANDLER_GO_ON; +} + + +#if 0 +/* dead code, might be reused somewhere again */ + +int file_cache_check_cache() { + file_cache_entry *first_unused_fce = NULL; + file_cache *fc = srv->file_cache; + size_t i; + + /* check the cache */ + for (i = 0; i < fc->used; i++) { + fce = fc->ptr[i]; + + if (buffer_is_equal(name, fce->name)) { + log_error_write(srv, __FILE__, __LINE__, "sb", "cache hit:", name); + +#ifdef USE_LINUX_SIGIO + if (fce->is_dirty == 0) { + fce->in_use++; + return fce; + } +#endif + + /* get the etag information */ + if (-1 == (stat(name->ptr, &(fce->st)))) { + fce->in_use = 0; + + log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed: ", name, strerror(errno)); + + file_cache_entry_reset(srv, fce); + + return NULL; + } + fce->stat_ts = srv->cur_ts; + + /* create etag */ + etag_create(srv->file_cache_etag, &(fce->st)); + + if (!buffer_is_equal(srv->file_cache_etag, fce->etag)) { + size_t s_len = 0, k; + /* etag has changed: reopen file */ + + file_cache_entry_reset(srv, fce); + + if (-1 == (fce->fd = open(fce->name->ptr, O_RDONLY | O_LARGEFILE))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); + + buffer_reset(fce->name); + return NULL; + } + + srv->cur_fds++; + + buffer_copy_string_buffer(fce->etag, srv->file_cache_etag); + + /* determine mimetype */ + buffer_reset(fce->content_type); + + s_len = name->used - 1; + + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + size_t ct_len; + + ct_len = ds->key->used - 1; + + if (buffer_is_equal_right_len(name, ds->key, ct_len)) { + buffer_copy_string_buffer(fce->content_type, ds->value); + break; + } + } + +#ifdef HAVE_XATTR + if (buffer_is_empty(fce->content_type)) { + fce_attr_get(fce->content_type, name->ptr); + } +#endif + } + +#ifdef USE_LINUX_SIGIO + fce->is_dirty = 0; +#endif + + fce->in_use++; + + if (fce->fd == -1) { + log_error_write(srv, __FILE__, __LINE__, "sb", "fd is still -1 !", fce->name); + } + if (fce->st.st_size == 0) { + log_error_write(srv, __FILE__, __LINE__, "sb", "size is still 0 !", fce->name); + } + + + + + return fce; + } + + if (fce->in_use == 0) { + if (!first_unused_fce) first_unused_fce = fce; + + if (srv->cur_ts - fce->stat_ts > 10) { + file_cache_entry_reset(srv, fce); + } + } + } + + if (first_unused_fce) { + file_cache_entry_reset(srv, fce); + + fce = first_unused_fce; + } else { + /* not found, insert */ + fce = file_cache_get_unused_entry(srv); + } +} +#endif + + +handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce_ptr) { + file_cache_entry *fce = NULL; + file_cache_entry *o_fce = *o_fce_ptr; + + + UNUSED(con); + + /* still valid ? */ + if (o_fce != NULL) { + if (buffer_is_equal(name, o_fce->name) && + (o_fce->fd != -1) && + (o_fce->stat_ts == srv->cur_ts) + ) { + return HANDLER_GO_ON; + } else { + o_fce->in_use--; + } + file_cache_entry_reset(srv, o_fce); + } + + + fce = file_cache_get_unused_entry(srv); + + buffer_copy_string_buffer(fce->name, name); + fce->in_use = 0; + fce->fd = -1; + + if (-1 == (con->conf.follow_symlink ? stat(name->ptr, &(fce->st)) : lstat(name->ptr, &(fce->st)))) { + int oerrno = errno; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sbs", "stat failed:", name, strerror(errno)); +#endif + file_cache_entry_reset(srv, fce); + + buffer_reset(fce->name); + + errno = oerrno; + return HANDLER_ERROR; + } + + fce->stat_ts = srv->cur_ts; + + if (S_ISREG(fce->st.st_mode)) { + size_t k, s_len; +#ifdef USE_LINUX_SIGIO + file_cache_entry *dir_fce; + char *slash; +#endif + + if (-1 == (fce->fd = open(name->ptr, O_RDONLY | O_LARGEFILE))) { + int oerrno = errno; + if (errno == EMFILE || errno == EINTR) { + return HANDLER_WAIT_FOR_FD; + } + + log_error_write(srv, __FILE__, __LINE__, "sbs", + "open failed for:", name, + strerror(errno)); + + buffer_reset(fce->name); + + errno = oerrno; + return HANDLER_ERROR; + } + + srv->cur_fds++; + + /* determine mimetype */ + buffer_reset(fce->content_type); + + s_len = name->used - 1; + + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + size_t ct_len; + + if (ds->key->used == 0) continue; + + ct_len = ds->key->used - 1; + + if (s_len < ct_len) continue; + + if (0 == strncmp(name->ptr + s_len - ct_len, ds->key->ptr, ct_len)) { + buffer_copy_string_buffer(fce->content_type, ds->value); + break; + } + } + +#ifdef HAVE_XATTR + if (buffer_is_empty(fce->content_type)) { + fce_attr_get(fce->content_type, name->ptr); + } +#endif + + etag_create(fce->etag, &(fce->st)); + +#ifdef USE_LINUX_SIGIO + /* register sigio for the directory */ + dir_fce = file_cache_get_unused_entry(srv); + + buffer_copy_string_buffer(fc->dir_name, name); + + /* get dirname */ + if (0 == (slash = strrchr(fc->dir_name->ptr, '/'))) { + SEGFAULT(); + } + *(slash+1) = '\0'; + + if (-1 == (dir_fce->fd = open(fc->dir_name->ptr, O_RDONLY))) { + int oerrno = errno; + log_error_write(srv, __FILE__, __LINE__, "sbs", + "open failed:", fc->dir_name, strerror(errno)); + + errno = oerrno; + return HANDLER_ERROR; + } + + srv->cur_fds++; + + if (fcntl(dir_fce->fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT) < 0) { + int oerrno = errno; + log_error_write(srv, __FILE__, __LINE__, "ss", + "fcntl failed:", strerror(errno)); + + close(dir_fce->fd); + srv->cur_fds--; + + errno = oerrno; + return HANDLER_ERROR; + } + + /* ->used is not updated -> no _buffer copy */ + buffer_copy_string(dir_fce->name, fc->dir_name->ptr); + + /* register fd-handler */ + fdevent_register(srv->ev, dir_fce->fd, file_cache_handle_fdevent, dir_fce); + fdevent_event_add(srv->ev, &(dir_fce->fde_ndx), dir_fce->fd, FDEVENT_IN); +# if 1 + log_error_write(srv, __FILE__, __LINE__, "sddb", "fdevent_event_add:", fce->fde_ndx, fce->fd, fce->name); +# endif +#endif + } + + fce->in_use++; + + *o_fce_ptr = fce; + + return HANDLER_GO_ON; +} + +int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce) { + UNUSED(srv); + UNUSED(con); + + if (fce->in_use > 0) fce->in_use--; + file_cache_entry_reset(srv, fce); + + return 0; +} + diff --git a/src/file_cache.h b/src/file_cache.h new file mode 100644 index 00000000..b45a7cea --- /dev/null +++ b/src/file_cache.h @@ -0,0 +1,12 @@ +#ifndef _FILE_CACHE_H_ +#define _FILE_CACHE_H_ + +#include "base.h" + +file_cache *file_cache_init(void); +void file_cache_free(server *srv, file_cache *fc); + +handler_t file_cache_get_entry(server *srv, connection *con, buffer *name, file_cache_entry **o_fce); +int file_cache_entry_release(server *srv, connection *con, file_cache_entry *fce); + +#endif diff --git a/src/http_auth.c b/src/http_auth.c new file mode 100644 index 00000000..2aa0e23f --- /dev/null +++ b/src/http_auth.c @@ -0,0 +1,939 @@ +#include "config.h" + +#ifdef HAVE_CRYPT_H +# include <crypt.h> +#elif defined(__linux__) +/* linux needs _XOPEN_SOURCE */ +# define _XOPEN_SOURCE +#endif + +#ifdef HAVE_LIBCRYPT +# define HAVE_CRYPT +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <ctype.h> + +#include "server.h" +#include "log.h" +#include "http_auth.h" +#include "http_auth_digest.h" +#include "stream.h" + +#ifdef USE_OPENSSL +# include <openssl/md5.h> +#else +# include "md5_global.h" +# include "md5.h" +#endif + + +#ifdef USE_PAM +#include <security/pam_appl.h> +#include <security/pam_misc.h> + +static struct pam_conv conv = { + misc_conv, + NULL +}; +#endif + +static const char base64_pad = '='; + +static const short base64_reverse_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + + +static unsigned char * base64_decode(buffer *out, const char *in) { + unsigned char *result; + int ch, j = 0, k; + size_t i; + + size_t in_len = strlen(in); + + buffer_prepare_copy(out, in_len); + + result = (unsigned char *)out->ptr; + + ch = in[0]; + /* run through the whole string, converting as we go */ + for (i = 0; i < in_len; i++) { + ch = in[i]; + + if (ch == '\0') break; + + if (ch == base64_pad) break; + + ch = base64_reverse_table[ch]; + if (ch < 0) continue; + + switch(i % 4) { + case 0: + result[j] = ch << 2; + break; + case 1: + result[j++] |= ch >> 4; + result[j] = (ch & 0x0f) << 4; + break; + case 2: + result[j++] |= ch >>2; + result[j] = (ch & 0x03) << 6; + break; + case 3: + result[j++] |= ch; + break; + } + } + k = j; + /* mop things up if we ended on a boundary */ + if (ch == base64_pad) { + switch(i % 4) { + case 0: + case 1: + return NULL; + case 2: + k++; + case 3: + result[k++] = 0; + } + } + result[k] = '\0'; + + out->used = k; + + return result; +} + +static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) { + int ret = -1; + + if (!username->used|| !realm->used) return -1; + + if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) { + stream f; + char * f_line; + + if (buffer_is_empty(p->conf.auth_htdigest_userfile)) return -1; + + if (0 != stream_open(&f, p->conf.auth_htdigest_userfile)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno)); + + return -1; + } + + f_line = f.start; + + while (f_line - f.start != f.size) { + char *f_user, *f_pwd, *e, *f_realm; + size_t u_len, pwd_len, r_len; + + f_user = f_line; + + /* + * htdigest format + * + * user:realm:md5(user:realm:password) + */ + + if (NULL == (f_realm = memchr(f_user, ':', f.size - (f_user - f.start) ))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "parsed error in", p->conf.auth_htdigest_userfile, + "expected 'username:realm:hashed password'"); + + stream_close(&f); + + return -1; + } + + if (NULL == (f_pwd = memchr(f_realm + 1, ':', f.size - (f_realm + 1 - f.start)))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "parsed error in", p->conf.auth_plain_userfile, + "expected 'username:realm:hashed password'"); + + stream_close(&f); + + return -1; + } + + /* get pointers to the fields */ + u_len = f_realm - f_user; + f_realm++; + r_len = f_pwd - f_realm; + f_pwd++; + + if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) { + pwd_len = e - f_pwd; + } else { + pwd_len = f.size - (f_pwd - f.start); + } + + if (username->used - 1 == u_len && + (realm->used - 1 == r_len) && + (0 == strncmp(username->ptr, f_user, u_len)) && + (0 == strncmp(realm->ptr, f_realm, r_len))) { + /* found */ + + buffer_copy_string_len(password, f_pwd, pwd_len); + + ret = 0; + break; + } + + /* EOL */ + if (!e) break; + + f_line = e + 1; + } + + stream_close(&f); + } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD || + p->conf.auth_backend == AUTH_BACKEND_PLAIN) { + stream f; + char * f_line; + buffer *auth_fn; + + auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile; + + if (buffer_is_empty(auth_fn)) return -1; + + if (0 != stream_open(&f, auth_fn)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening plain-userfile", auth_fn, "failed:", strerror(errno)); + + return -1; + } + + f_line = f.start; + + while (f_line - f.start != f.size) { + char *f_user, *f_pwd, *e; + size_t u_len, pwd_len; + + f_user = f_line; + + /* + * htpasswd format + * + * user:crypted passwd + */ + + if (NULL == (f_pwd = memchr(f_user, ':', f.size - (f_user - f.start) ))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "parsed error in", auth_fn, + "expected 'username:hashed password'"); + + stream_close(&f); + + return -1; + } + + /* get pointers to the fields */ + u_len = f_pwd - f_user; + f_pwd++; + + if (NULL != (e = memchr(f_pwd, '\n', f.size - (f_pwd - f.start)))) { + pwd_len = e - f_pwd; + } else { + pwd_len = f.size - (f_pwd - f.start); + } + + if (username->used - 1 == u_len && + (0 == strncmp(username->ptr, f_user, u_len))) { + /* found */ + + buffer_copy_string_len(password, f_pwd, pwd_len); + + ret = 0; + break; + } + + /* EOL */ + if (!e) break; + + f_line = e + 1; + } + + stream_close(&f); + } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) { + ret = 0; + } else { + return -1; + } + + return ret; +} + +static int http_auth_match_rules(server *srv, mod_auth_plugin_data *p, const char *url, const char *username, const char *group, const char *host) { + const char *r = NULL, *rules = NULL; + size_t i; + int username_len; + data_string *require; + array *req; + + UNUSED(group); + UNUSED(host); + + /* check what has to be match to fullfil the request */ + /* search auth-directives for path */ + for (i = 0; i < p->conf.auth_require->used; i++) { + if (p->conf.auth_require->data[i]->key->used == 0) continue; + + if (0 == strncmp(url, p->conf.auth_require->data[i]->key->ptr, p->conf.auth_require->data[i]->key->used - 1)) { + break; + } + } + + if (i == p->conf.auth_require->used) { + return -1; + } + + req = ((data_array *)(p->conf.auth_require->data[i]))->value; + + require = (data_string *)array_get_element(req, "require"); + + /* user=name1|group=name3|host=name4 */ + + /* seperate the string by | */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value); +#endif + + username_len = username ? strlen(username) : 0; + + r = rules = require->value->ptr; + + while (1) { + const char *eq; + const char *k, *v, *e; + int k_len, v_len, r_len; + + e = strchr(r, '|'); + + if (e) { + r_len = e - r; + } else { + r_len = strlen(rules) - (r - rules); + } + + /* from r to r + r_len is a rule */ + + /* search for = in the rules */ + if (NULL == (eq = strchr(r, '='))) { + log_error_write(srv, __FILE__, __LINE__, "s", "= is missing"); + return -1; + } + + /* = out of range */ + if (eq > r + r_len) { + log_error_write(srv, __FILE__, __LINE__, "s", "= out of range"); + + return -1; + } + + /* the part before the = is user|group|host */ + + k = r; + k_len = eq - r; + v = eq + 1; + v_len = r_len - k_len - 1; + + if (k_len == 4) { + if (0 == strncmp(k, "user", k_len)) { + if (username && + username_len == v_len && + 0 == strncmp(username, v, v_len)) { + return 0; + } + } else if (0 == strncmp(k, "host", k_len)) { + log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)"); + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown key"); + return -1; + } + } else if (k_len == 5) { + if (0 == strncmp(k, "group", k_len)) { + log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)"); + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k); + return -1; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown key"); + return -1; + } + + if (!e) break; + r = e + 1; + } + + log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched"); + + return -1; +} + +/** + * + * + * @param password password-string from the auth-backend + * @param pw password-string from the client + */ + +static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p, array *req, buffer *username, buffer *realm, buffer *password, const char *pw) { + UNUSED(srv); + UNUSED(req); + + if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) { + /* + * htdigest format + * + * user:realm:md5(user:realm:password) + */ + + MD5_CTX Md5Ctx; + HASH HA1; + char a1[256]; + + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)username->ptr, username->used - 1); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)realm->ptr, realm->used - 1); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw)); + MD5_Final(HA1, &Md5Ctx); + + CvtHex(HA1, a1); + + if (0 == strcmp(password->ptr, a1)) { + return 0; + } + } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) { +#ifdef HAVE_CRYPT + char salt[3]; + char *crypted; + /* + * htpasswd format + * + * user:crypted password + */ + + salt[0] = password->ptr[0]; + salt[1] = password->ptr[1]; + salt[2] = '\0'; + crypted = crypt(pw, salt); + + if (0 == strcmp(password->ptr, crypted)) { + return 0; + } +#endif + } else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) { + if (0 == strcmp(password->ptr, pw)) { + return 0; + } + } else if (p->conf.auth_backend == AUTH_BACKEND_PAM) { +#ifdef USE_PAM + pam_handle_t *pamh=NULL; + int retval; + + retval = pam_start("login", username->ptr, &conv, &pamh); + + if (retval == PAM_SUCCESS) + retval = pam_authenticate(pamh, 0); /* is user really user? */ + + if (retval == PAM_SUCCESS) + retval = pam_acct_mgmt(pamh, 0); /* permitted access? */ + + /* This is where we have been authorized or not. */ + + if (pam_end(pamh,retval) != PAM_SUCCESS) { /* close Linux-PAM */ + pamh = NULL; + log_error_write(srv, __FILE__, __LINE__, "s", "failed to release authenticator"); + } + + if (retval == PAM_SUCCESS) { + log_error_write(srv, __FILE__, __LINE__, "s", "Authenticated"); + return 0; + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "Not Authenticated"); + } +#endif + } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) { +#ifdef USE_LDAP + LDAP *ldap; + LDAPMessage *lm, *first; + char *dn; + int ret; + char *attrs[] = { LDAP_NO_ATTRS, NULL }; + size_t i; + + /* for now we stay synchronous */ + + /* + * 1. connect anonymously (done in plugin init) + * 2. get DN for uid = username + * 3. auth against ldap server + * 4. (optional) check a field + * 5. disconnect + * + */ + + /* check username + * + * we have to protect us againt username which modifies out filter in + * a unpleasant way + */ + + for (i = 0; i < username->used - 1; i++) { + char c = username->ptr[i]; + + if (!isalpha(c) && + !isdigit(c)) { + + log_error_write(srv, __FILE__, __LINE__, "sbd", + "ldap: invalid character (a-zA-Z0-9 allowed) in username:", username, i); + + return -1; + } + } + + + + /* build filter */ + buffer_copy_string_buffer(p->ldap_filter, p->conf.ldap_filter_pre); + buffer_append_string_buffer(p->ldap_filter, username); + buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post); + + + /* 2. */ + if (LDAP_SUCCESS != (ret = ldap_search_s(p->conf.ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) { + log_error_write(srv, __FILE__, __LINE__, "sssb", + "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter); + + return -1; + } + + if (NULL == (first = ldap_first_entry(p->conf.ldap, lm))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); + + ldap_msgfree(lm); + + return -1; + } + + if (NULL == (dn = ldap_get_dn(p->conf.ldap, first))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap ..."); + + ldap_msgfree(lm); + + return -1; + } + + ldap_msgfree(lm); + + + /* 3. */ + if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno)); + return -1; + } + + ret = LDAP_VERSION3; + if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + ldap_unbind_s(ldap); + + return -1; + } + + + if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + ldap_unbind_s(ldap); + + return -1; + } + + /* 5. */ + ldap_unbind_s(ldap); + + /* everything worked, good, access granted */ + + return 0; +#endif + } + return -1; +} + +int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) { + buffer *username, *password; + char *pw; + + data_string *realm; + + realm = (data_string *)array_get_element(req, "realm"); + + username = buffer_init(); + password = buffer_init(); + + base64_decode(username, realm_str); + + /* r2 == user:password */ + if (NULL == (pw = strchr(username->ptr, ':'))) { + buffer_free(username); + + log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username); + + return 0; + } + + *pw++ = '\0'; + + username->used = pw - username->ptr; + + /* copy password to r1 */ + if (http_auth_get_password(srv, p, username, realm->value, password)) { + buffer_free(username); + buffer_free(password); + + log_error_write(srv, __FILE__, __LINE__, "s", "get_password failed"); + + return 0; + } + + /* password doesn't match */ + if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) { + log_error_write(srv, __FILE__, __LINE__, "sbb", "password doesn't match", con->uri.path, username); + + buffer_free(username); + buffer_free(password); + + return 0; + } + + /* value is our allow-rules */ + if (http_auth_match_rules(srv, p, url->ptr, username->ptr, NULL, NULL)) { + buffer_free(username); + buffer_free(password); + + log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match"); + + return 0; + } + + /* remember the username */ + buffer_copy_string_buffer(p->auth_user, username); + + buffer_free(username); + buffer_free(password); + + return 1; +} + +typedef struct { + const char *key; + int key_len; + char **ptr; +} digest_kv; + +int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str) { + char a1[256]; + char a2[256]; + + char *username; + char *realm; + char *nonce; + char *uri; + char *algorithm; + char *qop; + char *cnonce; + char *nc; + char *respons; + + const char *method_get = "GET"; + const char *method_post = "POST"; + const char *method_head = "HEAD"; + + char *e, *c; + const char *m = NULL; + int i; + buffer *password, *b, *username_buf, *realm_buf; + + MD5_CTX Md5Ctx; + HASH HA1; + HASH HA2; + HASH RespHash; + HASHHEX HA2Hex; + + + /* init pointers */ +#define S(x) \ + x, sizeof(x)-1, NULL + digest_kv dkv[10] = { + { S("username=") }, + { S("realm=") }, + { S("nonce=") }, + { S("uri=") }, + { S("algorithm=") }, + { S("qop=") }, + { S("cnonce=") }, + { S("nc=") }, + { S("response=") }, + + { NULL, 0, NULL } + }; +#undef S + + dkv[0].ptr = &username; + dkv[1].ptr = &realm; + dkv[2].ptr = &nonce; + dkv[3].ptr = &uri; + dkv[4].ptr = &algorithm; + dkv[5].ptr = &qop; + dkv[6].ptr = &cnonce; + dkv[7].ptr = &nc; + dkv[8].ptr = &respons; + dkv[9].ptr = NULL; + + UNUSED(req); + + for (i = 0; dkv[i].key; i++) { + *(dkv[i].ptr) = NULL; + } + + + if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST && + p->conf.auth_backend != AUTH_BACKEND_PLAIN) { + log_error_write(srv, __FILE__, __LINE__, "s", + "digest: unsupported backend (only htdigest or plain)"); + + return -1; + } + + b = buffer_init_string(realm_str); + + /* parse credentials from client */ + for (c = b->ptr; *c; c++) { + for (i = 0; dkv[i].key; i++) { + if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) { + if ((c[dkv[i].key_len] == '"') && + (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) { + /* value with "..." */ + *(dkv[i].ptr) = c + dkv[i].key_len + 1; + c = e; + + *e = '\0'; + } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) { + /* value without "...", terminated by ',' */ + *(dkv[i].ptr) = c + dkv[i].key_len; + c = e; + + *e = '\0'; + } else { + /* value without "...", terminated by EOL */ + *(dkv[i].ptr) = c + dkv[i].key_len; + c += strlen(c) - 1; + } + } + } + } + + if (p->conf.auth_debug > 1) { + log_error_write(srv, __FILE__, __LINE__, "ss", "username", username); + log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm); + log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce); + log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri); + log_error_write(srv, __FILE__, __LINE__, "ss", "algorigthm", algorithm); + log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop); + log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce); + log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc); + log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons); + } + + /* check if everything is transmitted */ + if (!username || + !realm || + !nonce || + !uri || + (qop && !nc && !cnonce) || + !respons ) { + /* missing field */ + + log_error_write(srv, __FILE__, __LINE__, "s", + "digest: missing field"); + return -1; + } + + switch(con->request.http_method) { + case HTTP_METHOD_GET: m = method_get; break; + case HTTP_METHOD_POST: m = method_post; break; + case HTTP_METHOD_HEAD: m = method_head; break; + case HTTP_METHOD_UNSET: break; + } + + /* password-string == HA1 */ + password = buffer_init(); + username_buf = buffer_init_string(username); + realm_buf = buffer_init_string(realm); + if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) { + buffer_free(password); + buffer_free(b); + buffer_free(username_buf); + buffer_free(realm_buf); + return 0; + } + + buffer_free(username_buf); + buffer_free(realm_buf); + + if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) { + /* generate password from plain-text */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)password->ptr, password->used - 1); + MD5_Final(HA1, &Md5Ctx); + } else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) { + /* HA1 */ + /* transform the 32-byte-hex-md5 to a 16-byte-md5 */ + for (i = 0; i < HASHLEN; i++) { + HA1[i] = hex2int(password->ptr[i*2]) << 4; + HA1[i] |= hex2int(password->ptr[i*2+1]); + } + } else { + /* we already check that above */ + SEGFAULT(); + } + + buffer_free(password); + + if (algorithm && + strcasecmp(algorithm, "md5-sess") == 0) { + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)HA1, 16); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce)); + MD5_Final(HA1, &Md5Ctx); + } + + CvtHex(HA1, a1); + + /* calculate H(A2) */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri)); + if (strcasecmp(qop, "auth-int") == 0) { + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)"", HASHHEXLEN); + } + MD5_Final(HA2, &Md5Ctx); + CvtHex(HA2, HA2Hex); + + /* calculate response */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + if (*qop) { + MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + }; + MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN); + MD5_Final(RespHash, &Md5Ctx); + CvtHex(RespHash, a2); + + if (0 != strcmp(a2, respons)) { + /* digest not ok */ + + if (p->conf.auth_debug) { + log_error_write(srv, __FILE__, __LINE__, "ssssb", + "digest: digest mismatch", a2, respons); + } + + log_error_write(srv, __FILE__, __LINE__, "sss", + "digest: auth failed for", username, "wrong password"); + + buffer_free(b); + return 0; + } + + /* value is our allow-rules */ + if (http_auth_match_rules(srv, p, url->ptr, username, NULL, NULL)) { + buffer_free(b); + + log_error_write(srv, __FILE__, __LINE__, "s", + "digest: rules did match"); + + return 0; + } + + /* remember the username */ + buffer_copy_string(p->auth_user, username); + + buffer_free(b); + + if (p->conf.auth_debug) { + log_error_write(srv, __FILE__, __LINE__, "s", + "digest: auth ok"); + } + return 1; +} + + +int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char out[33]) { + HASH h; + MD5_CTX Md5Ctx; + char hh[32]; + + UNUSED(p); + + /* generate shared-secret */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)fn->ptr, fn->used - 1); + MD5_Update(&Md5Ctx, (unsigned char *)"+", 1); + + /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */ + ltostr(hh, srv->cur_ts); + MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); + ltostr(hh, rand()); + MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); + + MD5_Final(h, &Md5Ctx); + + CvtHex(h, out); + + return 0; +} diff --git a/src/http_auth.h b/src/http_auth.h new file mode 100644 index 00000000..392678b5 --- /dev/null +++ b/src/http_auth.h @@ -0,0 +1,64 @@ +#ifndef _HTTP_AUTH_H_ +#define _HTTP_AUTH_H_ + +#include "server.h" +#include "plugin.h" + +#if defined(HAVE_LDAP_H) && defined(HAVE_LBER_H) && defined(HAVE_LIBLDAP) && defined(HAVE_LIBLBER) +# define USE_LDAP +# include <ldap.h> +#endif + +typedef enum { AUTH_BACKEND_UNSET, AUTH_BACKEND_PLAIN, + AUTH_BACKEND_LDAP, AUTH_BACKEND_HTPASSWD, + AUTH_BACKEND_HTDIGEST, AUTH_BACKEND_PAM } auth_backend_t; + +typedef struct { + /* auth */ + array *auth_require; + + buffer *auth_plain_groupfile; + buffer *auth_plain_userfile; + + buffer *auth_htdigest_userfile; + buffer *auth_htpasswd_userfile; + + buffer *auth_backend_conf; + + buffer *auth_ldap_hostname; + buffer *auth_ldap_basedn; + buffer *auth_ldap_filter; + + unsigned short auth_debug; + + /* generated */ + auth_backend_t auth_backend; + +#ifdef USE_LDAP + LDAP *ldap; + + buffer *ldap_filter_pre; + buffer *ldap_filter_post; +#endif +} mod_auth_plugin_config; + +typedef struct { + PLUGIN_DATA; + buffer *tmp_buf; + + buffer *auth_user; + +#ifdef USE_LDAP + buffer *ldap_filter; +#endif + + mod_auth_plugin_config **config_storage; + + mod_auth_plugin_config conf; /* this is only used as long as no handler_ctx is setup */ +} mod_auth_plugin_data; + +int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str); +int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, buffer *url, const char *realm_str); +int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char hh[33]); + +#endif diff --git a/src/http_auth_digest.c b/src/http_auth_digest.c new file mode 100644 index 00000000..a1dd2d3d --- /dev/null +++ b/src/http_auth_digest.c @@ -0,0 +1,103 @@ +#include <string.h> +#include "http_auth_digest.h" + +#include "config.h" +#include "buffer.h" + +#ifndef USE_OPENSSL +# include "md5_global.h" +# include "md5.h" +#endif + +void CvtHex(IN HASH Bin, OUT HASHHEX Hex) { + unsigned short i; + + for (i = 0; i < HASHLEN; i++) { + Hex[i*2] = int2hex((Bin[i] >> 4) & 0xf); + Hex[i*2+1] = int2hex(Bin[i] & 0xf); + } + Hex[HASHHEXLEN] = '\0'; +} + +/* calculate H(A1) as per spec */ +void DigestCalcHA1( + IN char * pszAlg, + IN char * pszUserName, + IN char * pszRealm, + IN char * pszPassword, + IN char * pszNonce, + IN char * pszCNonce, + OUT HASHHEX SessionKey + ) +{ + MD5_CTX Md5Ctx; + HASH HA1; + + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)pszUserName, strlen(pszUserName)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszRealm, strlen(pszRealm)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszPassword, strlen(pszPassword)); + MD5_Final(HA1, &Md5Ctx); + if (strcasecmp(pszAlg, "md5-sess") == 0) { + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)HA1, HASHLEN); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszNonce, strlen(pszNonce)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszCNonce, strlen(pszCNonce)); + MD5_Final(HA1, &Md5Ctx); + } + CvtHex(HA1, SessionKey); +} + +/* calculate request-digest/response-digest as per HTTP Digest spec */ +void DigestCalcResponse( + IN HASHHEX HA1, /* H(A1) */ + IN char * pszNonce, /* nonce from server */ + IN char * pszNonceCount, /* 8 hex digits */ + IN char * pszCNonce, /* client nonce */ + IN char * pszQop, /* qop-value: "", "auth", "auth-int" */ + IN char * pszMethod, /* method from the request */ + IN char * pszDigestUri, /* requested URL */ + IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ + OUT HASHHEX Response /* request-digest or response-digest */ + ) +{ + MD5_CTX Md5Ctx; + HASH HA2; + HASH RespHash; + HASHHEX HA2Hex; + + /* calculate H(A2) */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)pszMethod, strlen(pszMethod)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszDigestUri, strlen(pszDigestUri)); + if (strcasecmp(pszQop, "auth-int") == 0) { + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)HEntity, HASHHEXLEN); + }; + MD5_Final(HA2, &Md5Ctx); + CvtHex(HA2, HA2Hex); + + /* calculate response */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)HA1, HASHHEXLEN); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszNonce, strlen(pszNonce)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + if (*pszQop) { + MD5_Update(&Md5Ctx, (unsigned char *)pszNonceCount, strlen(pszNonceCount)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszCNonce, strlen(pszCNonce)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + MD5_Update(&Md5Ctx, (unsigned char *)pszQop, strlen(pszQop)); + MD5_Update(&Md5Ctx, (unsigned char *)":", 1); + } + MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN); + MD5_Final(RespHash, &Md5Ctx); + CvtHex(RespHash, Response); +} + diff --git a/src/http_auth_digest.h b/src/http_auth_digest.h new file mode 100644 index 00000000..2d4e363f --- /dev/null +++ b/src/http_auth_digest.h @@ -0,0 +1,46 @@ +#ifndef _DIGCALC_H_ +#define _DIGCALC_H_ + +#include "config.h" + +#define HASHLEN 16 +typedef unsigned char HASH[HASHLEN]; +#define HASHHEXLEN 32 +typedef char HASHHEX[HASHHEXLEN+1]; +#ifdef USE_OPENSSL +#define IN const +#else +#define IN +#endif +#define OUT + +/* calculate H(A1) as per HTTP Digest spec */ +void DigestCalcHA1( + IN char * pszAlg, + IN char * pszUserName, + IN char * pszRealm, + IN char * pszPassword, + IN char * pszNonce, + IN char * pszCNonce, + OUT HASHHEX SessionKey + ); + +/* calculate request-digest/response-digest as per HTTP Digest spec */ +void DigestCalcResponse( + IN HASHHEX HA1, /* H(A1) */ + IN char * pszNonce, /* nonce from server */ + IN char * pszNonceCount, /* 8 hex digits */ + IN char * pszCNonce, /* client nonce */ + IN char * pszQop, /* qop-value: "", "auth", "auth-int" */ + IN char * pszMethod, /* method from the request */ + IN char * pszDigestUri, /* requested URL */ + IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ + OUT HASHHEX Response /* request-digest or response-digest */ + ); + +void CvtHex( + IN HASH Bin, + OUT HASHHEX Hex + ); + +#endif diff --git a/src/http_chunk.c b/src/http_chunk.c new file mode 100644 index 00000000..c128bf1c --- /dev/null +++ b/src/http_chunk.c @@ -0,0 +1,133 @@ +/** + * the HTTP chunk-API + * + * + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> + +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include "server.h" +#include "chunk.h" +#include "http_chunk.h" +#include "log.h" + +static int http_chunk_append_len(server *srv, connection *con, size_t len) { + size_t i, olen = len, j; + buffer *b; + + b = srv->tmp_chunk_len; + + if (len == 0) { + buffer_copy_string(b, "0"); + } else { + for (i = 0; i < 8 && len; i++) { + len >>= 4; + } + + /* i is the number of hex digits we have */ + buffer_prepare_copy(b, i + 1); + + for (j = i-1, len = olen; j+1 > 0; j--) { + b->ptr[j] = (len & 0xf) + (((len & 0xf) <= 9) ? '0' : 'a' - 10); + len >>= 4; + } + b->used = i; + b->ptr[b->used++] = '\0'; + } + + buffer_append_string(b, "\r\n"); + chunkqueue_append_buffer(con->write_queue, b); + + return 0; +} + + +int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len) { + chunkqueue *cq; + + if (!con) return -1; + + cq = con->write_queue; + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + http_chunk_append_len(srv, con, len); + } + + chunkqueue_append_file(cq, fn, offset, len); + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) { + chunkqueue_append_mem(cq, "\r\n", 2 + 1); + } + + return 0; +} + +int http_chunk_append_buffer(server *srv, connection *con, buffer *mem) { + chunkqueue *cq; + + if (!con) return -1; + + cq = con->write_queue; + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + http_chunk_append_len(srv, con, mem->used - 1); + } + + chunkqueue_append_buffer(cq, mem); + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && mem->used > 0) { + chunkqueue_append_mem(cq, "\r\n", 2 + 1); + } + + return 0; +} + +int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len) { + chunkqueue *cq; + + if (!con) return -1; + + cq = con->write_queue; + + if (len == 0) { + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + http_chunk_append_len(srv, con, 0); + chunkqueue_append_mem(cq, "\r\n", 2 + 1); + } else { + chunkqueue_append_mem(cq, "", 1); + } + return 0; + } + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + http_chunk_append_len(srv, con, len - 1); + } + + chunkqueue_append_mem(cq, mem, len); + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + chunkqueue_append_mem(cq, "\r\n", 2 + 1); + } + + return 0; +} + + +off_t http_chunkqueue_length(server *srv, connection *con) { + if (!con) { + log_error_write(srv, __FILE__, __LINE__, "s", "connection is NULL!!"); + + return 0; + } + + return chunkqueue_length(con->write_queue); +} diff --git a/src/http_chunk.h b/src/http_chunk.h new file mode 100644 index 00000000..4ba24a26 --- /dev/null +++ b/src/http_chunk.h @@ -0,0 +1,12 @@ +#ifndef _HTTP_CHUNK_H_ +#define _HTTP_CHUNK_H_ + +#include "server.h" +#include <sys/types.h> + +int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len); +int http_chunk_append_buffer(server *srv, connection *con, buffer *mem); +int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len); +off_t http_chunkqueue_length(server *srv, connection *con); + +#endif diff --git a/src/inet_ntop_cache.c b/src/inet_ntop_cache.c new file mode 100644 index 00000000..c0b3aa18 --- /dev/null +++ b/src/inet_ntop_cache.c @@ -0,0 +1,53 @@ +#include <sys/types.h> + +#include <string.h> + + +#include "base.h" +#include "inet_ntop_cache.h" +#include "sys-socket.h" + +const char * inet_ntop_cache_get_ip(server *srv, sock_addr *addr) { +#ifdef HAVE_IPV6 + size_t ndx = 0, i; + for (i = 0; i < INET_NTOP_CACHE_MAX; i++) { + if (srv->inet_ntop_cache[i].ts != 0) { + if (srv->inet_ntop_cache[i].family == AF_INET6 && + 0 == memcmp(srv->inet_ntop_cache[i].addr.ipv6.s6_addr, addr->ipv6.sin6_addr.s6_addr, 16)) { + /* IPv6 found in cache */ + break; + } else if (srv->inet_ntop_cache[i].family == AF_INET && + srv->inet_ntop_cache[i].addr.ipv4.s_addr == addr->ipv4.sin_addr.s_addr) { + /* IPv4 found in cache */ + break; + + } + } + } + + if (i == INET_NTOP_CACHE_MAX) { + /* not found in cache */ + + i = ndx; + inet_ntop(addr->plain.sa_family, + addr->plain.sa_family == AF_INET6 ? + (const void *) &(addr->ipv6.sin6_addr) : + (const void *) &(addr->ipv4.sin_addr), + srv->inet_ntop_cache[i].b2, INET6_ADDRSTRLEN); + + srv->inet_ntop_cache[i].ts = srv->cur_ts; + srv->inet_ntop_cache[i].family = addr->plain.sa_family; + + if (srv->inet_ntop_cache[i].family == AF_INET) { + srv->inet_ntop_cache[i].addr.ipv4.s_addr = addr->ipv4.sin_addr.s_addr; + } else if (srv->inet_ntop_cache[i].family == AF_INET6) { + memcpy(srv->inet_ntop_cache[i].addr.ipv6.s6_addr, addr->ipv6.sin6_addr.s6_addr, 16); + } + } + + return srv->inet_ntop_cache[i].b2; +#else + UNUSED(srv); + return inet_ntoa(addr->ipv4.sin_addr); +#endif +} diff --git a/src/inet_ntop_cache.h b/src/inet_ntop_cache.h new file mode 100644 index 00000000..fd3c2814 --- /dev/null +++ b/src/inet_ntop_cache.h @@ -0,0 +1,7 @@ +#ifndef _INET_NTOP_CACHE_H_ +#define _INET_NTOP_CACHE_H_ + +#include "base.h" +const char * inet_ntop_cache_get_ip(server *srv, sock_addr *addr); + +#endif diff --git a/src/joblist.c b/src/joblist.c new file mode 100644 index 00000000..dcab9550 --- /dev/null +++ b/src/joblist.c @@ -0,0 +1,63 @@ +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "joblist.h" +#include "log.h" + +int joblist_append(server *srv, connection *con) { + if (con->in_joblist) return 0; + + if (srv->joblist->size == 0) { + srv->joblist->size = 16; + srv->joblist->ptr = malloc(sizeof(*srv->joblist->ptr) * srv->joblist->size); + } else if (srv->joblist->used == srv->joblist->size) { + srv->joblist->size += 16; + srv->joblist->ptr = realloc(srv->joblist->ptr, sizeof(*srv->joblist->ptr) * srv->joblist->size); + } + + srv->joblist->ptr[srv->joblist->used++] = con; + + return 0; +} + +void joblist_free(server *srv, connections *joblist) { + UNUSED(srv); + + free(joblist->ptr); + free(joblist); +} + +connection *fdwaitqueue_unshift(server *srv, connections *fdwaitqueue) { + connection *con; + UNUSED(srv); + + + if (fdwaitqueue->used == 0) return NULL; + + con = fdwaitqueue->ptr[0]; + + memmove(fdwaitqueue->ptr, &(fdwaitqueue->ptr[1]), --fdwaitqueue->used * sizeof(*(fdwaitqueue->ptr))); + + return con; +} + +int fdwaitqueue_append(server *srv, connection *con) { + if (srv->fdwaitqueue->size == 0) { + srv->fdwaitqueue->size = 16; + srv->fdwaitqueue->ptr = malloc(sizeof(*(srv->fdwaitqueue->ptr)) * srv->fdwaitqueue->size); + } else if (srv->fdwaitqueue->used == srv->fdwaitqueue->size) { + srv->fdwaitqueue->size += 16; + srv->fdwaitqueue->ptr = realloc(srv->fdwaitqueue->ptr, sizeof(*(srv->fdwaitqueue->ptr)) * srv->fdwaitqueue->size); + } + + srv->fdwaitqueue->ptr[srv->fdwaitqueue->used++] = con; + + return 0; +} + +void fdwaitqueue_free(server *srv, connections *fdwaitqueue) { + UNUSED(srv); + free(fdwaitqueue->ptr); + free(fdwaitqueue); +} diff --git a/src/joblist.h b/src/joblist.h new file mode 100644 index 00000000..3701e097 --- /dev/null +++ b/src/joblist.h @@ -0,0 +1,13 @@ +#ifndef _JOB_LIST_H_ +#define _JOB_LIST_H_ + +#include "base.h" + +int joblist_append(server *srv, connection *con); +void joblist_free(server *srv, connections *joblist); + +int fdwaitqueue_append(server *srv, connection *con); +void fdwaitqueue_free(server *srv, connections *fdwaitqueue); +connection *fdwaitqueue_unshift(server *srv, connections *fdwaitqueue); + +#endif diff --git a/src/keyvalue.c b/src/keyvalue.c new file mode 100644 index 00000000..1b8eaf8d --- /dev/null +++ b/src/keyvalue.c @@ -0,0 +1,352 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "server.h" +#include "keyvalue.h" + +static keyvalue http_versions[] = { + { HTTP_VERSION_1_1, "HTTP/1.1" }, + { HTTP_VERSION_1_0, "HTTP/1.0" }, + { HTTP_VERSION_UNSET, NULL } +}; + +static keyvalue http_methods[] = { + { HTTP_METHOD_GET, "GET" }, + { HTTP_METHOD_POST, "POST" }, + { HTTP_METHOD_HEAD, "HEAD" }, + { HTTP_METHOD_UNSET, NULL } +}; + +static keyvalue http_status[] = { + { 100, "Continue" }, + { 101, "Switching Protocols" }, + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non-Authoritative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, + { 302, "Found" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 305, "Use Proxy" }, + { 306, "(Unused)" }, + { 307, "Temporary Redirect" }, + { 400, "Bad Request" }, + { 401, "Unauthorized" }, + { 402, "Payment Required" }, + { 403, "Forbidden" }, + { 404, "Not Found" }, + { 405, "Method Not Allowed" }, + { 406, "Not Acceptable" }, + { 407, "Proxy Authentication Required" }, + { 408, "Request Timeout" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Request Entity Too Large" }, + { 414, "Request-URI Too Long" }, + { 415, "Unsupported Media Type" }, + { 416, "Requested Range Not Satisfiable" }, + { 417, "Expectation Failed" }, + { 426, "Upgrade Required" }, + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Not Available" }, + { 504, "Gateway Timeout" }, + { 505, "HTTP Version Not Supported" }, + + { -1, NULL } +}; + +static keyvalue http_status_body[] = { + { 400, "400.html" }, + { 401, "401.html" }, + { 403, "403.html" }, + { 404, "404.html" }, + { 411, "411.html" }, + { 416, "416.html" }, + { 500, "500.html" }, + { 501, "501.html" }, + { 503, "503.html" }, + { 505, "505.html" }, + + { -1, NULL } +}; + + +const char *keyvalue_get_value(keyvalue *kv, int k) { + int i; + for (i = 0; kv[i].value; i++) { + if (kv[i].key == k) return kv[i].value; + } + return NULL; +} + +int keyvalue_get_key(keyvalue *kv, const char *s) { + int i; + for (i = 0; kv[i].value; i++) { + if (0 == strcmp(kv[i].value, s)) return kv[i].key; + } + return -1; +} + +keyvalue_buffer *keyvalue_buffer_init(void) { + keyvalue_buffer *kvb; + + kvb = calloc(1, sizeof(*kvb)); + + return kvb; +} + +int keyvalue_buffer_append(keyvalue_buffer *kvb, int key, const char *value) { + size_t i; + if (kvb->size == 0) { + kvb->size = 4; + + kvb->kv = malloc(kvb->size * sizeof(*kvb->kv)); + + for(i = 0; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } else if (kvb->used == kvb->size) { + kvb->size += 4; + + kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv)); + + for(i = kvb->used; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } + + kvb->kv[kvb->used]->key = key; + kvb->kv[kvb->used]->value = strdup(value); + + kvb->used++; + + return 0; +} + +void keyvalue_buffer_free(keyvalue_buffer *kvb) { + size_t i; + + for (i = 0; i < kvb->size; i++) { + if (kvb->kv[i]->value) free(kvb->kv[i]->value); + free(kvb->kv[i]); + } + + if (kvb->kv) free(kvb->kv); + + free(kvb); +} + + +s_keyvalue_buffer *s_keyvalue_buffer_init(void) { + s_keyvalue_buffer *kvb; + + kvb = calloc(1, sizeof(*kvb)); + + return kvb; +} + +int s_keyvalue_buffer_append(s_keyvalue_buffer *kvb, const char *key, const char *value) { + size_t i; + if (kvb->size == 0) { + kvb->size = 4; + kvb->used = 0; + + kvb->kv = malloc(kvb->size * sizeof(*kvb->kv)); + + for(i = 0; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } else if (kvb->used == kvb->size) { + kvb->size += 4; + + kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv)); + + for(i = kvb->used; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } + + kvb->kv[kvb->used]->key = key ? strdup(key) : NULL; + kvb->kv[kvb->used]->value = strdup(value); + + kvb->used++; + + return 0; +} + +void s_keyvalue_buffer_free(s_keyvalue_buffer *kvb) { + size_t i; + + for (i = 0; i < kvb->size; i++) { + if (kvb->kv[i]->key) free(kvb->kv[i]->key); + if (kvb->kv[i]->value) free(kvb->kv[i]->value); + free(kvb->kv[i]); + } + + if (kvb->kv) free(kvb->kv); + + free(kvb); +} + + +httpauth_keyvalue_buffer *httpauth_keyvalue_buffer_init(void) { + httpauth_keyvalue_buffer *kvb; + + kvb = calloc(1, sizeof(*kvb)); + + return kvb; +} + +int httpauth_keyvalue_buffer_append(httpauth_keyvalue_buffer *kvb, const char *key, const char *realm, httpauth_type type) { + size_t i; + if (kvb->size == 0) { + kvb->size = 4; + + kvb->kv = malloc(kvb->size * sizeof(*kvb->kv)); + + for(i = 0; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } else if (kvb->used == kvb->size) { + kvb->size += 4; + + kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv)); + + for(i = kvb->used; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } + + kvb->kv[kvb->used]->key = strdup(key); + kvb->kv[kvb->used]->realm = strdup(realm); + kvb->kv[kvb->used]->type = type; + + kvb->used++; + + return 0; +} + +void httpauth_keyvalue_buffer_free(httpauth_keyvalue_buffer *kvb) { + size_t i; + + for (i = 0; i < kvb->size; i++) { + if (kvb->kv[i]->key) free(kvb->kv[i]->key); + if (kvb->kv[i]->realm) free(kvb->kv[i]->realm); + free(kvb->kv[i]); + } + + if (kvb->kv) free(kvb->kv); + + free(kvb); +} + + +const char *get_http_version_name(int i) { + return keyvalue_get_value(http_versions, i); +} + +const char *get_http_status_name(int i) { + return keyvalue_get_value(http_status, i); +} + +const char *get_http_method_name(http_method_t i) { + return keyvalue_get_value(http_methods, i); +} + +const char *get_http_status_body_name(int i) { + return keyvalue_get_value(http_status_body, i); +} + +int get_http_version_key(const char *s) { + return keyvalue_get_key(http_versions, s); +} + +http_method_t get_http_method_key(const char *s) { + return (http_method_t)keyvalue_get_key(http_methods, s); +} + + + + +pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void) { + pcre_keyvalue_buffer *kvb; + + kvb = calloc(1, sizeof(*kvb)); + + return kvb; +} + +int pcre_keyvalue_buffer_append(pcre_keyvalue_buffer *kvb, const char *key, const char *value) { +#ifdef HAVE_PCRE_H + size_t i; + const char *errptr; + int erroff; +#endif + + if (!key) return -1; + +#ifdef HAVE_PCRE_H + if (kvb->size == 0) { + kvb->size = 4; + kvb->used = 0; + + kvb->kv = malloc(kvb->size * sizeof(*kvb->kv)); + + for(i = 0; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } else if (kvb->used == kvb->size) { + kvb->size += 4; + + kvb->kv = realloc(kvb->kv, kvb->size * sizeof(*kvb->kv)); + + for(i = kvb->used; i < kvb->size; i++) { + kvb->kv[i] = calloc(1, sizeof(**kvb->kv)); + } + } + + if (NULL == (kvb->kv[kvb->used]->key = pcre_compile(key, + 0, &errptr, &erroff, NULL))) { + + fprintf(stderr, "%s.%d: rexexp compilation error at %s\n", __FILE__, __LINE__, errptr); + return -1; + } + + kvb->kv[kvb->used]->value = strdup(value); + + kvb->used++; + + return 0; +#else + UNUSED(kvb); + UNUSED(value); + + return -1; +#endif +} + +void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb) { +#ifdef HAVE_PCRE_H + size_t i; + + for (i = 0; i < kvb->size; i++) { + if (kvb->kv[i]->key) pcre_free(kvb->kv[i]->key); + if (kvb->kv[i]->value) free(kvb->kv[i]->value); + free(kvb->kv[i]); + } + + if (kvb->kv) free(kvb->kv); +#endif + + free(kvb); +} diff --git a/src/keyvalue.h b/src/keyvalue.h new file mode 100644 index 00000000..3c1f0519 --- /dev/null +++ b/src/keyvalue.h @@ -0,0 +1,80 @@ +#ifndef _KEY_VALUE_H_ +#define _KEY_VALUE_H_ + +#include "config.h" + +#ifdef HAVE_PCRE_H +# include <pcre.h> +#endif + +typedef enum { HTTP_METHOD_UNSET = -1, HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD } http_method_t; +typedef enum { HTTP_VERSION_UNSET = -1, HTTP_VERSION_1_0, HTTP_VERSION_1_1 } http_version_t; + +typedef struct { + int key; + + char *value; +} keyvalue; + +typedef struct { + char *key; + + char *value; +} s_keyvalue; + +typedef struct { +#ifdef HAVE_PCRE_H + pcre *key; +#endif + + char *value; +} pcre_keyvalue; + +typedef enum { HTTP_AUTH_BASIC, HTTP_AUTH_DIGEST } httpauth_type; + +typedef struct { + char *key; + + char *realm; + httpauth_type type; +} httpauth_keyvalue; + +#define KVB(x) \ +typedef struct {\ + x **kv; \ + size_t used;\ + size_t size;\ +} x ## _buffer + +KVB(keyvalue); +KVB(s_keyvalue); +KVB(httpauth_keyvalue); +KVB(pcre_keyvalue); + +const char *get_http_status_name(int i); +const char *get_http_version_name(int i); +const char *get_http_method_name(http_method_t i); +const char *get_http_status_body_name(int i); +int get_http_version_key(const char *s); +http_method_t get_http_method_key(const char *s); + +const char *keyvalue_get_value(keyvalue *kv, int k); +int keyvalue_get_key(keyvalue *kv, const char *s); + +keyvalue_buffer *keyvalue_buffer_init(void); +int keyvalue_buffer_append(keyvalue_buffer *kvb, int k, const char *value); +void keyvalue_buffer_free(keyvalue_buffer *kvb); + +s_keyvalue_buffer *s_keyvalue_buffer_init(void); +int s_keyvalue_buffer_append(s_keyvalue_buffer *kvb, const char *key, const char *value); +void s_keyvalue_buffer_free(s_keyvalue_buffer *kvb); + +httpauth_keyvalue_buffer *httpauth_keyvalue_buffer_init(void); +int httpauth_keyvalue_buffer_append(httpauth_keyvalue_buffer *kvb, const char *key, const char *realm, httpauth_type type); +void httpauth_keyvalue_buffer_free(httpauth_keyvalue_buffer *kvb); + +pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void); +int pcre_keyvalue_buffer_append(pcre_keyvalue_buffer *kvb, const char *key, const char *value); +void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb); + +#endif diff --git a/src/lemon.c b/src/lemon.c new file mode 100644 index 00000000..55e558db --- /dev/null +++ b/src/lemon.c @@ -0,0 +1,4397 @@ +/* +** This file contains all sources (including headers) to the LEMON +** LALR(1) parser generator. The sources have been combined into a +** single file to make it easy to include LEMON in the source tree +** and Makefile of another program. +** +** The author of this program disclaims copyright. +*/ +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +extern void qsort(); +extern double strtod(); +extern long strtol(); +extern void free(); +extern int access(); +extern int atoi(); + +#ifndef __WIN32__ +# if defined(_WIN32) || defined(WIN32) +# define __WIN32__ +# endif +#endif + +/* #define PRIVATE static */ +#define PRIVATE + +#ifdef TEST +#define MAXRHS 5 /* Set low to exercise exception code */ +#else +#define MAXRHS 1000 +#endif + +char *msort(); +extern void *malloc(); + +/******** From the file "action.h" *************************************/ +struct action *Action_new(); +struct action *Action_sort(); +void Action_add(); + +/********* From the file "assert.h" ************************************/ +void myassert(); +#ifndef NDEBUG +# define assert(X) if(!(X))myassert(__FILE__,__LINE__) +#else +# define assert(X) +#endif + +/********** From the file "build.h" ************************************/ +void FindRulePrecedences(); +void FindFirstSets(); +void FindStates(); +void FindLinks(); +void FindFollowSets(); +void FindActions(); + +/********* From the file "configlist.h" *********************************/ +void Configlist_init(/* void */); +struct config *Configlist_add(/* struct rule *, int */); +struct config *Configlist_addbasis(/* struct rule *, int */); +void Configlist_closure(/* void */); +void Configlist_sort(/* void */); +void Configlist_sortbasis(/* void */); +struct config *Configlist_return(/* void */); +struct config *Configlist_basis(/* void */); +void Configlist_eat(/* struct config * */); +void Configlist_reset(/* void */); + +/********* From the file "error.h" ***************************************/ +void ErrorMsg(const char *, int,const char *, ...); + +/****** From the file "option.h" ******************************************/ +struct s_options { + enum { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR, + OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type; + char *label; + char *arg; + char *message; +}; +int OptInit(/* char**,struct s_options*,FILE* */); +int OptNArgs(/* void */); +char *OptArg(/* int */); +void OptErr(/* int */); +void OptPrint(/* void */); + +/******** From the file "parse.h" *****************************************/ +void Parse(/* struct lemon *lemp */); + +/********* From the file "plink.h" ***************************************/ +struct plink *Plink_new(/* void */); +void Plink_add(/* struct plink **, struct config * */); +void Plink_copy(/* struct plink **, struct plink * */); +void Plink_delete(/* struct plink * */); + +/********** From the file "report.h" *************************************/ +void Reprint(/* struct lemon * */); +void ReportOutput(/* struct lemon * */); +void ReportTable(/* struct lemon * */); +void ReportHeader(/* struct lemon * */); +void CompressTables(/* struct lemon * */); + +/********** From the file "set.h" ****************************************/ +void SetSize(/* int N */); /* All sets will be of size N */ +char *SetNew(/* void */); /* A new set for element 0..N */ +void SetFree(/* char* */); /* Deallocate a set */ + +int SetAdd(/* char*,int */); /* Add element to a set */ +int SetUnion(/* char *A,char *B */); /* A <- A U B, thru element N */ + +#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */ + +/********** From the file "struct.h" *************************************/ +/* +** Principal data structures for the LEMON parser generator. +*/ + +typedef enum {B_FALSE=0, B_TRUE} Boolean; + +/* Symbols (terminals and nonterminals) of the grammar are stored +** in the following: */ +struct symbol { + char *name; /* Name of the symbol */ + int index; /* Index number for this symbol */ + enum { + TERMINAL, + NONTERMINAL + } type; /* Symbols are all either TERMINALS or NTs */ + struct rule *rule; /* Linked list of rules of this (if an NT) */ + struct symbol *fallback; /* fallback token in case this token doesn't parse */ + int prec; /* Precedence if defined (-1 otherwise) */ + enum e_assoc { + LEFT, + RIGHT, + NONE, + UNK + } assoc; /* Associativity if predecence is defined */ + char *firstset; /* First-set for all rules of this symbol */ + Boolean lambda; /* True if NT and can generate an empty string */ + char *destructor; /* Code which executes whenever this symbol is + ** popped from the stack during error processing */ + int destructorln; /* Line number of destructor code */ + char *datatype; /* The data type of information held by this + ** object. Only used if type==NONTERMINAL */ + int dtnum; /* The data type number. In the parser, the value + ** stack is a union. The .yy%d element of this + ** union is the correct data type for this object */ +}; + +/* Each production rule in the grammar is stored in the following +** structure. */ +struct rule { + struct symbol *lhs; /* Left-hand side of the rule */ + char *lhsalias; /* Alias for the LHS (NULL if none) */ + int ruleline; /* Line number for the rule */ + int nrhs; /* Number of RHS symbols */ + struct symbol **rhs; /* The RHS symbols */ + char **rhsalias; /* An alias for each RHS symbol (NULL if none) */ + int line; /* Line number at which code begins */ + char *code; /* The code executed when this rule is reduced */ + struct symbol *precsym; /* Precedence symbol for this rule */ + int index; /* An index number for this rule */ + Boolean canReduce; /* True if this rule is ever reduced */ + struct rule *nextlhs; /* Next rule with the same LHS */ + struct rule *next; /* Next rule in the global list */ +}; + +/* A configuration is a production rule of the grammar together with +** a mark (dot) showing how much of that rule has been processed so far. +** Configurations also contain a follow-set which is a list of terminal +** symbols which are allowed to immediately follow the end of the rule. +** Every configuration is recorded as an instance of the following: */ +struct config { + struct rule *rp; /* The rule upon which the configuration is based */ + int dot; /* The parse point */ + char *fws; /* Follow-set for this configuration only */ + struct plink *fplp; /* Follow-set forward propagation links */ + struct plink *bplp; /* Follow-set backwards propagation links */ + struct state *stp; /* Pointer to state which contains this */ + enum { + COMPLETE, /* The status is used during followset and */ + INCOMPLETE /* shift computations */ + } status; + struct config *next; /* Next configuration in the state */ + struct config *bp; /* The next basis configuration */ +}; + +/* Every shift or reduce operation is stored as one of the following */ +struct action { + struct symbol *sp; /* The look-ahead symbol */ + enum e_action { + SHIFT, + ACCEPT, + REDUCE, + ERROR, + CONFLICT, /* Was a reduce, but part of a conflict */ + SH_RESOLVED, /* Was a shift. Precedence resolved conflict */ + RD_RESOLVED, /* Was reduce. Precedence resolved conflict */ + NOT_USED /* Deleted by compression */ + } type; + union { + struct state *stp; /* The new state, if a shift */ + struct rule *rp; /* The rule, if a reduce */ + } x; + struct action *next; /* Next action for this state */ + struct action *collide; /* Next action with the same hash */ +}; + +/* Each state of the generated parser's finite state machine +** is encoded as an instance of the following structure. */ +struct state { + struct config *bp; /* The basis configurations for this state */ + struct config *cfp; /* All configurations in this set */ + int index; /* Sequencial number for this state */ + struct action *ap; /* Array of actions for this state */ + int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */ + int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */ + int iDflt; /* Default action */ +}; +#define NO_OFFSET (-2147483647) + +/* A followset propagation link indicates that the contents of one +** configuration followset should be propagated to another whenever +** the first changes. */ +struct plink { + struct config *cfp; /* The configuration to which linked */ + struct plink *next; /* The next propagate link */ +}; + +/* The state vector for the entire parser generator is recorded as +** follows. (LEMON uses no global variables and makes little use of +** static variables. Fields in the following structure can be thought +** of as begin global variables in the program.) */ +struct lemon { + struct state **sorted; /* Table of states sorted by state number */ + struct rule *rule; /* List of all rules */ + int nstate; /* Number of states */ + int nrule; /* Number of rules */ + int nsymbol; /* Number of terminal and nonterminal symbols */ + int nterminal; /* Number of terminal symbols */ + struct symbol **symbols; /* Sorted array of pointers to symbols */ + int errorcnt; /* Number of errors */ + struct symbol *errsym; /* The error symbol */ + char *name; /* Name of the generated parser */ + char *arg; /* Declaration of the 3th argument to parser */ + char *tokentype; /* Type of terminal symbols in the parser stack */ + char *vartype; /* The default type of non-terminal symbols */ + char *start; /* Name of the start symbol for the grammar */ + char *stacksize; /* Size of the parser stack */ + char *include; /* Code to put at the start of the C file */ + int includeln; /* Line number for start of include code */ + char *error; /* Code to execute when an error is seen */ + int errorln; /* Line number for start of error code */ + char *overflow; /* Code to execute on a stack overflow */ + int overflowln; /* Line number for start of overflow code */ + char *failure; /* Code to execute on parser failure */ + int failureln; /* Line number for start of failure code */ + char *accept; /* Code to execute when the parser excepts */ + int acceptln; /* Line number for the start of accept code */ + char *extracode; /* Code appended to the generated file */ + int extracodeln; /* Line number for the start of the extra code */ + char *tokendest; /* Code to execute to destroy token data */ + int tokendestln; /* Line number for token destroyer code */ + char *vardest; /* Code for the default non-terminal destructor */ + int vardestln; /* Line number for default non-term destructor code*/ + char *filename; /* Name of the input file */ + char *tmplname; /* Name of the template file */ + char *outname; /* Name of the current output file */ + char *tokenprefix; /* A prefix added to token names in the .h file */ + int nconflict; /* Number of parsing conflicts */ + int tablesize; /* Size of the parse tables */ + int basisflag; /* Print only basis configurations */ + int has_fallback; /* True if any %fallback is seen in the grammer */ + char *argv0; /* Name of the program */ +}; + +#define MemoryCheck(X) if((X)==0){ \ + extern void memory_error(); \ + memory_error(); \ +} + +/**************** From the file "table.h" *********************************/ +/* +** All code in this file has been automatically generated +** from a specification in the file +** "table.q" +** by the associative array code building program "aagen". +** Do not edit this file! Instead, edit the specification +** file, then rerun aagen. +*/ +/* +** Code for processing tables in the LEMON parser generator. +*/ + +/* Routines for handling a strings */ + +char *Strsafe(); + +void Strsafe_init(/* void */); +int Strsafe_insert(/* char * */); +char *Strsafe_find(/* char * */); + +/* Routines for handling symbols of the grammar */ + +struct symbol *Symbol_new(); +int Symbolcmpp(/* struct symbol **, struct symbol ** */); +void Symbol_init(/* void */); +int Symbol_insert(/* struct symbol *, char * */); +struct symbol *Symbol_find(/* char * */); +struct symbol *Symbol_Nth(/* int */); +int Symbol_count(/* */); +struct symbol **Symbol_arrayof(/* */); + +/* Routines to manage the state table */ + +int Configcmp(/* struct config *, struct config * */); +struct state *State_new(); +void State_init(/* void */); +int State_insert(/* struct state *, struct config * */); +struct state *State_find(/* struct config * */); +struct state **State_arrayof(/* */); + +/* Routines used for efficiency in Configlist_add */ + +void Configtable_init(/* void */); +int Configtable_insert(/* struct config * */); +struct config *Configtable_find(/* struct config * */); +void Configtable_clear(/* int(*)(struct config *) */); +/****************** From the file "action.c" *******************************/ +/* +** Routines processing parser actions in the LEMON parser generator. +*/ + +/* Allocate a new parser action */ +struct action *Action_new(){ + static struct action *freelist = 0; + struct action *new; + + if( freelist==0 ){ + int i; + int amt = 100; + freelist = (struct action *)malloc( sizeof(struct action)*amt ); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new parser action."); + exit(1); + } + for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1]; + freelist[amt-1].next = 0; + } + new = freelist; + freelist = freelist->next; + return new; +} + +/* Compare two actions */ +static int actioncmp(ap1,ap2) +struct action *ap1; +struct action *ap2; +{ + int rc; + rc = ap1->sp->index - ap2->sp->index; + if( rc==0 ) rc = (int)ap1->type - (int)ap2->type; + if( rc==0 ){ + assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT); + assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT); + rc = ap1->x.rp->index - ap2->x.rp->index; + } + return rc; +} + +/* Sort parser actions */ +struct action *Action_sort(ap) +struct action *ap; +{ + ap = (struct action *)msort(ap,&ap->next,actioncmp); + return ap; +} + +void Action_add(app,type,sp,arg) +struct action **app; +enum e_action type; +struct symbol *sp; +char *arg; +{ + struct action *new; + new = Action_new(); + new->next = *app; + *app = new; + new->type = type; + new->sp = sp; + if( type==SHIFT ){ + new->x.stp = (struct state *)arg; + }else{ + new->x.rp = (struct rule *)arg; + } +} +/********************** New code to implement the "acttab" module ***********/ +/* +** This module implements routines use to construct the yy_action[] table. +*/ + +/* +** The state of the yy_action table under construction is an instance of +** the following structure +*/ +typedef struct acttab acttab; +struct acttab { + int nAction; /* Number of used slots in aAction[] */ + int nActionAlloc; /* Slots allocated for aAction[] */ + struct { + int lookahead; /* Value of the lookahead token */ + int action; /* Action to take on the given lookahead */ + } *aAction, /* The yy_action[] table under construction */ + *aLookahead; /* A single new transaction set */ + int mnLookahead; /* Minimum aLookahead[].lookahead */ + int mnAction; /* Action associated with mnLookahead */ + int mxLookahead; /* Maximum aLookahead[].lookahead */ + int nLookahead; /* Used slots in aLookahead[] */ + int nLookaheadAlloc; /* Slots allocated in aLookahead[] */ +}; + +/* Return the number of entries in the yy_action table */ +#define acttab_size(X) ((X)->nAction) + +/* The value for the N-th entry in yy_action */ +#define acttab_yyaction(X,N) ((X)->aAction[N].action) + +/* The value for the N-th entry in yy_lookahead */ +#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead) + +/* Free all memory associated with the given acttab */ +void acttab_free(acttab *p){ + free( p->aAction ); + free( p->aLookahead ); + free( p ); +} + +/* Allocate a new acttab structure */ +acttab *acttab_alloc(void){ + acttab *p = malloc( sizeof(*p) ); + if( p==0 ){ + fprintf(stderr,"Unable to allocate memory for a new acttab."); + exit(1); + } + memset(p, 0, sizeof(*p)); + return p; +} + +/* Add a new action to the current transaction set +*/ +void acttab_action(acttab *p, int lookahead, int action){ + if( p->nLookahead>=p->nLookaheadAlloc ){ + p->nLookaheadAlloc += 25; + p->aLookahead = realloc( p->aLookahead, + sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); + if( p->aLookahead==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + } + if( p->nLookahead==0 ){ + p->mxLookahead = lookahead; + p->mnLookahead = lookahead; + p->mnAction = action; + }else{ + if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead; + if( p->mnLookahead>lookahead ){ + p->mnLookahead = lookahead; + p->mnAction = action; + } + } + p->aLookahead[p->nLookahead].lookahead = lookahead; + p->aLookahead[p->nLookahead].action = action; + p->nLookahead++; +} + +/* +** Add the transaction set built up with prior calls to acttab_action() +** into the current action table. Then reset the transaction set back +** to an empty set in preparation for a new round of acttab_action() calls. +** +** Return the offset into the action table of the new transaction. +*/ +int acttab_insert(acttab *p){ + int i, j, k, n; + assert( p->nLookahead>0 ); + + /* Make sure we have enough space to hold the expanded action table + ** in the worst case. The worst case occurs if the transaction set + ** must be appended to the current action table + */ + n = p->mxLookahead + 1; + if( p->nAction + n >= p->nActionAlloc ){ + int oldAlloc = p->nActionAlloc; + p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; + p->aAction = realloc( p->aAction, + sizeof(p->aAction[0])*p->nActionAlloc); + if( p->aAction==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=oldAlloc; i<p->nActionAlloc; i++){ + p->aAction[i].lookahead = -1; + p->aAction[i].action = -1; + } + } + + /* Scan the existing action table looking for an offset where we can + ** insert the current transaction set. Fall out of the loop when that + ** offset is found. In the worst case, we fall out of the loop when + ** i reaches p->nAction, which means we append the new transaction set. + ** + ** i is the index in p->aAction[] where p->mnLookahead is inserted. + */ + for(i=0; i<p->nAction+p->mnLookahead; i++){ + if( p->aAction[i].lookahead<0 ){ + for(j=0; j<p->nLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 ) break; + if( p->aAction[k].lookahead>=0 ) break; + } + if( j<p->nLookahead ) continue; + for(j=0; j<p->nAction; j++){ + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break; + } + if( j==p->nAction ){ + break; /* Fits in empty slots */ + } + }else if( p->aAction[i].lookahead==p->mnLookahead ){ + if( p->aAction[i].action!=p->mnAction ) continue; + for(j=0; j<p->nLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 || k>=p->nAction ) break; + if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break; + if( p->aLookahead[j].action!=p->aAction[k].action ) break; + } + if( j<p->nLookahead ) continue; + n = 0; + for(j=0; j<p->nAction; j++){ + if( p->aAction[j].lookahead<0 ) continue; + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++; + } + if( n==p->nLookahead ){ + break; /* Same as a prior transaction set */ + } + } + } + /* Insert transaction set at index i. */ + for(j=0; j<p->nLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + p->aAction[k] = p->aLookahead[j]; + if( k>=p->nAction ) p->nAction = k+1; + } + p->nLookahead = 0; + + /* Return the offset that is added to the lookahead in order to get the + ** index into yy_action of the action */ + return i - p->mnLookahead; +} + +/********************** From the file "assert.c" ****************************/ +/* +** A more efficient way of handling assertions. +*/ +void myassert(file,line) +char *file; +int line; +{ + fprintf(stderr,"Assertion failed on line %d of file \"%s\"\n",line,file); + exit(1); +} +/********************** From the file "build.c" *****************************/ +/* +** Routines to construction the finite state machine for the LEMON +** parser generator. +*/ + +/* Find a precedence symbol of every rule in the grammar. +** +** Those rules which have a precedence symbol coded in the input +** grammar using the "[symbol]" construct will already have the +** rp->precsym field filled. Other rules take as their precedence +** symbol the first RHS symbol with a defined precedence. If there +** are not RHS symbols with a defined precedence, the precedence +** symbol field is left blank. +*/ +void FindRulePrecedences(xp) +struct lemon *xp; +{ + struct rule *rp; + for(rp=xp->rule; rp; rp=rp->next){ + if( rp->precsym==0 ){ + int i; + for(i=0; i<rp->nrhs; i++){ + if( rp->rhs[i]->prec>=0 ){ + rp->precsym = rp->rhs[i]; + break; + } + } + } + } + return; +} + +/* Find all nonterminals which will generate the empty string. +** Then go back and compute the first sets of every nonterminal. +** The first set is the set of all terminal symbols which can begin +** a string generated by that nonterminal. +*/ +void FindFirstSets(lemp) +struct lemon *lemp; +{ + int i; + struct rule *rp; + int progress; + + for(i=0; i<lemp->nsymbol; i++){ + lemp->symbols[i]->lambda = B_FALSE; + } + for(i=lemp->nterminal; i<lemp->nsymbol; i++){ + lemp->symbols[i]->firstset = SetNew(); + } + + /* First compute all lambdas */ + do{ + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->lhs->lambda ) continue; + for(i=0; i<rp->nrhs; i++){ + if( rp->rhs[i]->lambda==B_FALSE ) break; + } + if( i==rp->nrhs ){ + rp->lhs->lambda = B_TRUE; + progress = 1; + } + } + }while( progress ); + + /* Now compute all first sets */ + do{ + struct symbol *s1, *s2; + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + s1 = rp->lhs; + for(i=0; i<rp->nrhs; i++){ + s2 = rp->rhs[i]; + if( s2->type==TERMINAL ){ + progress += SetAdd(s1->firstset,s2->index); + break; + }else if( s1==s2 ){ + if( s1->lambda==B_FALSE ) break; + }else{ + progress += SetUnion(s1->firstset,s2->firstset); + if( s2->lambda==B_FALSE ) break; + } + } + } + }while( progress ); + return; +} + +/* Compute all LR(0) states for the grammar. Links +** are added to between some states so that the LR(1) follow sets +** can be computed later. +*/ +PRIVATE struct state *getstate(/* struct lemon * */); /* forward reference */ +void FindStates(lemp) +struct lemon *lemp; +{ + struct symbol *sp; + struct rule *rp; + + Configlist_init(); + + /* Find the start symbol */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ){ + ErrorMsg(lemp->filename,0, +"The specified start symbol \"%s\" is not \ +in a nonterminal of the grammar. \"%s\" will be used as the start \ +symbol instead.",lemp->start,lemp->rule->lhs->name); + lemp->errorcnt++; + sp = lemp->rule->lhs; + } + }else{ + sp = lemp->rule->lhs; + } + + /* Make sure the start symbol doesn't occur on the right-hand side of + ** any rule. Report an error if it does. (YACC would generate a new + ** start symbol in this case.) */ + for(rp=lemp->rule; rp; rp=rp->next){ + int i; + for(i=0; i<rp->nrhs; i++){ + if( rp->rhs[i]==sp ){ + ErrorMsg(lemp->filename,0, +"The start symbol \"%s\" occurs on the \ +right-hand side of a rule. This will result in a parser which \ +does not work properly.",sp->name); + lemp->errorcnt++; + } + } + } + + /* The basis configuration set for the first state + ** is all rules which have the start symbol as their + ** left-hand side */ + for(rp=sp->rule; rp; rp=rp->nextlhs){ + struct config *newcfp; + newcfp = Configlist_addbasis(rp,0); + SetAdd(newcfp->fws,0); + } + + /* Compute the first state. All other states will be + ** computed automatically during the computation of the first one. + ** The returned pointer to the first state is not used. */ + (void)getstate(lemp); + return; +} + +/* Return a pointer to a state which is described by the configuration +** list which has been built from calls to Configlist_add. +*/ +PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */ +PRIVATE struct state *getstate(lemp) +struct lemon *lemp; +{ + struct config *cfp, *bp; + struct state *stp; + + /* Extract the sorted basis of the new state. The basis was constructed + ** by prior calls to "Configlist_addbasis()". */ + Configlist_sortbasis(); + bp = Configlist_basis(); + + /* Get a state with the same basis */ + stp = State_find(bp); + if( stp ){ + /* A state with the same basis already exists! Copy all the follow-set + ** propagation links from the state under construction into the + ** preexisting state, then return a pointer to the preexisting state */ + struct config *x, *y; + for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){ + Plink_copy(&y->bplp,x->bplp); + Plink_delete(x->fplp); + x->fplp = x->bplp = 0; + } + cfp = Configlist_return(); + Configlist_eat(cfp); + }else{ + /* This really is a new state. Construct all the details */ + Configlist_closure(lemp); /* Compute the configuration closure */ + Configlist_sort(); /* Sort the configuration closure */ + cfp = Configlist_return(); /* Get a pointer to the config list */ + stp = State_new(); /* A new state structure */ + MemoryCheck(stp); + stp->bp = bp; /* Remember the configuration basis */ + stp->cfp = cfp; /* Remember the configuration closure */ + stp->index = lemp->nstate++; /* Every state gets a sequence number */ + stp->ap = 0; /* No actions, yet. */ + State_insert(stp,stp->bp); /* Add to the state table */ + buildshifts(lemp,stp); /* Recursively compute successor states */ + } + return stp; +} + +/* Construct all successor states to the given state. A "successor" +** state is any state which can be reached by a shift action. +*/ +PRIVATE void buildshifts(lemp,stp) +struct lemon *lemp; +struct state *stp; /* The state from which successors are computed */ +{ + struct config *cfp; /* For looping thru the config closure of "stp" */ + struct config *bcfp; /* For the inner loop on config closure of "stp" */ + struct config *new; /* */ + struct symbol *sp; /* Symbol following the dot in configuration "cfp" */ + struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */ + struct state *newstp; /* A pointer to a successor state */ + + /* Each configuration becomes complete after it contibutes to a successor + ** state. Initially, all configurations are incomplete */ + for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE; + + /* Loop through all configurations of the state "stp" */ + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */ + if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */ + Configlist_reset(); /* Reset the new config set */ + sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */ + + /* For every configuration in the state "stp" which has the symbol "sp" + ** following its dot, add the same configuration to the basis set under + ** construction but with the dot shifted one symbol to the right. */ + for(bcfp=cfp; bcfp; bcfp=bcfp->next){ + if( bcfp->status==COMPLETE ) continue; /* Already used */ + if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */ + bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */ + if( bsp!=sp ) continue; /* Must be same as for "cfp" */ + bcfp->status = COMPLETE; /* Mark this config as used */ + new = Configlist_addbasis(bcfp->rp,bcfp->dot+1); + Plink_add(&new->bplp,bcfp); + } + + /* Get a pointer to the state described by the basis configuration set + ** constructed in the preceding loop */ + newstp = getstate(lemp); + + /* The state "newstp" is reached from the state "stp" by a shift action + ** on the symbol "sp" */ + Action_add(&stp->ap,SHIFT,sp,newstp); + } +} + +/* +** Construct the propagation links +*/ +void FindLinks(lemp) +struct lemon *lemp; +{ + int i; + struct config *cfp, *other; + struct state *stp; + struct plink *plp; + + /* Housekeeping detail: + ** Add to every propagate link a pointer back to the state to + ** which the link is attached. */ + for(i=0; i<lemp->nstate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + cfp->stp = stp; + } + } + + /* Convert all backlinks into forward links. Only the forward + ** links are used in the follow-set computation. */ + for(i=0; i<lemp->nstate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + for(plp=cfp->bplp; plp; plp=plp->next){ + other = plp->cfp; + Plink_add(&other->fplp,cfp); + } + } + } +} + +/* Compute all followsets. +** +** A followset is the set of all symbols which can come immediately +** after a configuration. +*/ +void FindFollowSets(lemp) +struct lemon *lemp; +{ + int i; + struct config *cfp; + struct plink *plp; + int progress; + int change; + + for(i=0; i<lemp->nstate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + cfp->status = INCOMPLETE; + } + } + + do{ + progress = 0; + for(i=0; i<lemp->nstate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; + for(plp=cfp->fplp; plp; plp=plp->next){ + change = SetUnion(plp->cfp->fws,cfp->fws); + if( change ){ + plp->cfp->status = INCOMPLETE; + progress = 1; + } + } + cfp->status = COMPLETE; + } + } + }while( progress ); +} + +static int resolve_conflict(); + +/* Compute the reduce actions, and resolve conflicts. +*/ +void FindActions(lemp) +struct lemon *lemp; +{ + int i,j; + struct config *cfp; + struct state *stp; + struct symbol *sp; + struct rule *rp; + + /* Add all of the reduce actions + ** A reduce action is added for each element of the followset of + ** a configuration which has its dot at the extreme right. + */ + for(i=0; i<lemp->nstate; i++){ /* Loop over all states */ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */ + if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */ + for(j=0; j<lemp->nterminal; j++){ + if( SetFind(cfp->fws,j) ){ + /* Add a reduce action to the state "stp" which will reduce by the + ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */ + Action_add(&stp->ap,REDUCE,lemp->symbols[j],cfp->rp); + } + } + } + } + } + + /* Add the accepting token */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ) sp = lemp->rule->lhs; + }else{ + sp = lemp->rule->lhs; + } + /* Add to the first state (which is always the starting state of the + ** finite state machine) an action to ACCEPT if the lookahead is the + ** start nonterminal. */ + Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0); + + /* Resolve conflicts */ + for(i=0; i<lemp->nstate; i++){ + struct action *ap, *nap; + struct state *stp; + stp = lemp->sorted[i]; + assert( stp->ap ); + stp->ap = Action_sort(stp->ap); + for(ap=stp->ap; ap && ap->next; ap=ap->next){ + for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){ + /* The two actions "ap" and "nap" have the same lookahead. + ** Figure out which one should be used */ + lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym); + } + } + } + + /* Report an error for each rule that can never be reduced. */ + for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = B_FALSE; + for(i=0; i<lemp->nstate; i++){ + struct action *ap; + for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ + if( ap->type==REDUCE ) ap->x.rp->canReduce = B_TRUE; + } + } + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->canReduce ) continue; + ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n"); + lemp->errorcnt++; + } +} + +/* Resolve a conflict between the two given actions. If the +** conflict can't be resolve, return non-zero. +** +** NO LONGER TRUE: +** To resolve a conflict, first look to see if either action +** is on an error rule. In that case, take the action which +** is not associated with the error rule. If neither or both +** actions are associated with an error rule, then try to +** use precedence to resolve the conflict. +** +** If either action is a SHIFT, then it must be apx. This +** function won't work if apx->type==REDUCE and apy->type==SHIFT. +*/ +static int resolve_conflict(apx,apy,errsym) +struct action *apx; +struct action *apy; +struct symbol *errsym; /* The error symbol (if defined. NULL otherwise) */ +{ + struct symbol *spx, *spy; + int errcnt = 0; + assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */ + if( apx->type==SHIFT && apy->type==REDUCE ){ + spx = apx->sp; + spy = apy->x.rp->precsym; + if( spy==0 || spx->prec<0 || spy->prec<0 ){ + /* Not enough precedence information. */ + apy->type = CONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ /* Lower precedence wins */ + apy->type = RD_RESOLVED; + }else if( spx->prec<spy->prec ){ + apx->type = SH_RESOLVED; + }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */ + apy->type = RD_RESOLVED; /* associativity */ + }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */ + apx->type = SH_RESOLVED; + }else{ + assert( spx->prec==spy->prec && spx->assoc==NONE ); + apy->type = CONFLICT; + errcnt++; + } + }else if( apx->type==REDUCE && apy->type==REDUCE ){ + spx = apx->x.rp->precsym; + spy = apy->x.rp->precsym; + if( spx==0 || spy==0 || spx->prec<0 || + spy->prec<0 || spx->prec==spy->prec ){ + apy->type = CONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ + apy->type = RD_RESOLVED; + }else if( spx->prec<spy->prec ){ + apx->type = RD_RESOLVED; + } + }else{ + assert( + apx->type==SH_RESOLVED || + apx->type==RD_RESOLVED || + apx->type==CONFLICT || + apy->type==SH_RESOLVED || + apy->type==RD_RESOLVED || + apy->type==CONFLICT + ); + /* The REDUCE/SHIFT case cannot happen because SHIFTs come before + ** REDUCEs on the list. If we reach this point it must be because + ** the parser conflict had already been resolved. */ + } + return errcnt; +} +/********************* From the file "configlist.c" *************************/ +/* +** Routines to processing a configuration list and building a state +** in the LEMON parser generator. +*/ + +static struct config *freelist = 0; /* List of free configurations */ +static struct config *current = 0; /* Top of list of configurations */ +static struct config **currentend = 0; /* Last on list of configs */ +static struct config *basis = 0; /* Top of list of basis configs */ +static struct config **basisend = 0; /* End of list of basis configs */ + +/* Return a pointer to a new configuration */ +PRIVATE struct config *newconfig(){ + struct config *new; + if( freelist==0 ){ + int i; + int amt = 3; + freelist = (struct config *)malloc( sizeof(struct config)*amt ); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new configuration."); + exit(1); + } + for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1]; + freelist[amt-1].next = 0; + } + new = freelist; + freelist = freelist->next; + return new; +} + +/* The configuration "old" is no longer used */ +PRIVATE void deleteconfig(old) +struct config *old; +{ + old->next = freelist; + freelist = old; +} + +/* Initialized the configuration list builder */ +void Configlist_init(){ + current = 0; + currentend = ¤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==B_FALSE ) break; + } + } + if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp); + } + } + } + return; +} + +/* Sort the configuration list */ +void Configlist_sort(){ + current = (struct config *)msort(current,&(current->next),Configcmp); + currentend = 0; + return; +} + +/* Sort the basis configuration list */ +void Configlist_sortbasis(){ + basis = (struct config *)msort(current,&(current->bp),Configcmp); + basisend = 0; + return; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_return(){ + struct config *old; + old = current; + current = 0; + currentend = 0; + return old; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_basis(){ + struct config *old; + old = basis; + basis = 0; + basisend = 0; + return old; +} + +/* Free all elements of the given configuration list */ +void Configlist_eat(cfp) +struct config *cfp; +{ + struct config *nextcfp; + for(; cfp; cfp=nextcfp){ + nextcfp = cfp->next; + assert( cfp->fplp==0 ); + assert( cfp->bplp==0 ); + if( cfp->fws ) SetFree(cfp->fws); + deleteconfig(cfp); + } + return; +} +/***************** From the file "error.c" *********************************/ +/* +** Code for printing error message. +*/ + +/* Find a good place to break "msg" so that its length is at least "min" +** but no more than "max". Make the point as close to max as possible. +*/ +static int findbreak(msg,min,max) +char *msg; +int min; +int max; +{ + int i,spot; + char c; + for(i=spot=min; i<=max; i++){ + c = msg[i]; + if( c=='\t' ) msg[i] = ' '; + if( c=='\n' ){ msg[i] = ' '; spot = i; break; } + if( c==0 ){ spot = i; break; } + if( c=='-' && i<max-1 ) spot = i+1; + if( c==' ' ) spot = i; + } + return spot; +} + +/* +** The error message is split across multiple lines if necessary. The +** splits occur at a space, if there is a space available near the end +** of the line. +*/ +#define ERRMSGSIZE 10000 /* Hope this is big enough. No way to error check */ +#define LINEWIDTH 79 /* Max width of any output line */ +#define PREFIXLIMIT 30 /* Max width of the prefix on each line */ +void ErrorMsg(const char *filename, int lineno, const char *format, ...){ + char errmsg[ERRMSGSIZE]; + char prefix[PREFIXLIMIT+10]; + int errmsgsize; + int prefixsize; + int availablewidth; + va_list ap; + int end, restart, base; + + va_start(ap, format); + /* Prepare a prefix to be prepended to every output line */ + if( lineno>0 ){ + sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno); + }else{ + sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename); + } + prefixsize = strlen(prefix); + availablewidth = LINEWIDTH - prefixsize; + + /* Generate the error message */ + vsprintf(errmsg,format,ap); + va_end(ap); + errmsgsize = strlen(errmsg); + /* Remove trailing '\n's from the error message. */ + while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){ + errmsg[--errmsgsize] = 0; + } + + /* Print the error message */ + base = 0; + while( errmsg[base]!=0 ){ + end = restart = findbreak(&errmsg[base],0,availablewidth); + restart += base; + while( errmsg[restart]==' ' ) restart++; + fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]); + base = restart; + } +} +/**************** From the file "main.c" ************************************/ +/* +** Main program file for the LEMON parser generator. +*/ + +/* Report an out-of-memory condition and abort. This function +** is used mostly by the "MemoryCheck" macro in struct.h +*/ +void memory_error(){ + fprintf(stderr,"Out of memory. Aborting...\n"); + exit(1); +} + + +/* The main program. Parse the command line and do it... */ +int main(argc,argv) +int argc; +char **argv; +{ + static int version = 0; + static int rpflag = 0; + static int basisflag = 0; + static int compress = 0; + static int quiet = 0; + static int statistics = 0; + static int mhflag = 0; + static struct s_options options[] = { + {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."}, + {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."}, + {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."}, + {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file"}, + {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."}, + {OPT_FLAG, "s", (char*)&statistics, "Print parser stats to standard output."}, + {OPT_FLAG, "x", (char*)&version, "Print the version number."}, + {OPT_FLAG,0,0,0} + }; + int i; + struct lemon lem; + char *def_tmpl_name = "lempar.c"; + + OptInit(argv,options,stderr); + if( version ){ + printf("Lemon version 1.0\n"); + exit(0); + } + if( OptNArgs() < 1 ){ + fprintf(stderr,"Exactly one filename argument is required.\n"); + exit(1); + } + lem.errorcnt = 0; + + /* Initialize the machine */ + Strsafe_init(); + Symbol_init(); + State_init(); + lem.argv0 = argv[0]; + lem.filename = OptArg(0); + lem.tmplname = (OptNArgs() == 2) ? OptArg(1) : def_tmpl_name; + lem.basisflag = basisflag; + lem.has_fallback = 0; + lem.nconflict = 0; + lem.name = lem.include = lem.arg = lem.tokentype = lem.start = 0; + lem.vartype = 0; + lem.stacksize = 0; + lem.error = lem.overflow = lem.failure = lem.accept = lem.tokendest = + lem.tokenprefix = lem.outname = lem.extracode = 0; + lem.vardest = 0; + lem.tablesize = 0; + Symbol_new("$"); + lem.errsym = Symbol_new("error"); + + /* Parse the input file */ + Parse(&lem); + if( lem.errorcnt ) exit(lem.errorcnt); + if( lem.rule==0 ){ + fprintf(stderr,"Empty grammar.\n"); + exit(1); + } + + /* Count and index the symbols of the grammar */ + lem.nsymbol = Symbol_count(); + Symbol_new("{default}"); + lem.symbols = Symbol_arrayof(); + for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i; + qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*), + (int(*)())Symbolcmpp); + for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i; + for(i=1; isupper(lem.symbols[i]->name[0]); i++); + lem.nterminal = i; + + /* Generate a reprint of the grammar, if requested on the command line */ + if( rpflag ){ + Reprint(&lem); + }else{ + /* Initialize the size for all follow and first sets */ + SetSize(lem.nterminal); + + /* Find the precedence for every production rule (that has one) */ + FindRulePrecedences(&lem); + + /* Compute the lambda-nonterminals and the first-sets for every + ** nonterminal */ + FindFirstSets(&lem); + + /* Compute all LR(0) states. Also record follow-set propagation + ** links so that the follow-set can be computed later */ + lem.nstate = 0; + FindStates(&lem); + lem.sorted = State_arrayof(); + + /* Tie up loose ends on the propagation links */ + FindLinks(&lem); + + /* Compute the follow set of every reducible configuration */ + FindFollowSets(&lem); + + /* Compute the action tables */ + FindActions(&lem); + + /* Compress the action tables */ + if( compress==0 ) CompressTables(&lem); + + /* Generate a report of the parser generated. (the "y.output" file) */ + if( !quiet ) ReportOutput(&lem); + + /* Generate the source code for the parser */ + ReportTable(&lem, mhflag); + + /* Produce a header file for use by the scanner. (This step is + ** omitted if the "-m" option is used because makeheaders will + ** generate the file for us.) */ + if( !mhflag ) ReportHeader(&lem); + } + if( statistics ){ + printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n", + lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule); + printf(" %d states, %d parser table entries, %d conflicts\n", + lem.nstate, lem.tablesize, lem.nconflict); + } + if( lem.nconflict ){ + fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); + } + exit(lem.errorcnt + lem.nconflict); +} +/******************** From the file "msort.c" *******************************/ +/* +** A generic merge-sort program. +** +** USAGE: +** Let "ptr" be a pointer to some structure which is at the head of +** a null-terminated list. Then to sort the list call: +** +** ptr = msort(ptr,&(ptr->next),cmpfnc); +** +** In the above, "cmpfnc" is a pointer to a function which compares +** two instances of the structure and returns an integer, as in +** strcmp. The second argument is a pointer to the pointer to the +** second element of the linked list. This address is used to compute +** the offset to the "next" field within the structure. The offset to +** the "next" field must be constant for all structures in the list. +** +** The function returns a new pointer which is the head of the list +** after sorting. +** +** ALGORITHM: +** Merge-sort. +*/ + +/* +** Return a pointer to the next structure in the linked list. +*/ +#define NEXT(A) (*(char**)(((unsigned long)A)+offset)) + +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** offset: Offset in the structure to the "next" field. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next" pointers for elements in the lists a and b are +** changed. +*/ +static char *merge(a,b,cmp,offset) +char *a; +char *b; +int (*cmp)(); +int offset; +{ + char *ptr, *head; + + if( a==0 ){ + head = b; + }else if( b==0 ){ + head = a; + }else{ + if( (*cmp)(a,b)<0 ){ + ptr = a; + a = NEXT(a); + }else{ + ptr = b; + b = NEXT(b); + } + head = ptr; + while( a && b ){ + if( (*cmp)(a,b)<0 ){ + NEXT(ptr) = a; + ptr = a; + a = NEXT(a); + }else{ + NEXT(ptr) = b; + ptr = b; + b = NEXT(b); + } + } + if( a ) NEXT(ptr) = a; + else NEXT(ptr) = b; + } + return head; +} + +/* +** Inputs: +** list: Pointer to a singly-linked list of structures. +** next: Pointer to pointer to the second element of the list. +** cmp: A comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** orginally in list. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define LISTSIZE 30 +char *msort(list,next,cmp) +char *list; +char **next; +int (*cmp)(); +{ + unsigned long offset; + char *ep; + char *set[LISTSIZE]; + int i; + offset = (unsigned long)next - (unsigned long)list; + for(i=0; i<LISTSIZE; i++) set[i] = 0; + while( list ){ + ep = list; + list = NEXT(list); + NEXT(ep) = 0; + for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){ + ep = merge(ep,set[i],cmp,offset); + set[i] = 0; + } + set[i] = ep; + } + ep = 0; + for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(ep,set[i],cmp,offset); + return ep; +} +/************************ From the file "option.c" **************************/ +static char **argv; +static struct s_options *op; +static FILE *errstream; + +#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0) + +/* +** Print the command line with a carrot pointing to the k-th character +** of the n-th field. +*/ +static void errline(n,k,err) +int n; +int k; +FILE *err; +{ + int spcnt, i; + spcnt = 0; + if( argv[0] ) fprintf(err,"%s",argv[0]); + spcnt = strlen(argv[0]) + 1; + for(i=1; i<n && argv[i]; i++){ + fprintf(err," %s",argv[i]); + spcnt += strlen(argv[i]+1); + } + spcnt += k; + for(; argv[i]; i++) fprintf(err," %s",argv[i]); + if( spcnt<20 ){ + fprintf(err,"\n%*s^-- here\n",spcnt,""); + }else{ + fprintf(err,"\n%*shere --^\n",spcnt-7,""); + } +} + +/* +** Return the index of the N-th non-switch argument. Return -1 +** if N is out of range. +*/ +static int argindex(n) +int n; +{ + int i; + int dashdash = 0; + if( argv!=0 && *argv!=0 ){ + for(i=1; argv[i]; i++){ + if( dashdash || !ISOPT(argv[i]) ){ + if( n==0 ) return i; + n--; + } + if( strcmp(argv[i],"--")==0 ) dashdash = 1; + } + } + return -1; +} + +static char emsg[] = "Command line syntax error: "; + +/* +** Process a flag command line argument. +*/ +static int handleflags(i,err) +int i; +FILE *err; +{ + int v; + int errcnt = 0; + int j; + for(j=0; op[j].label; j++){ + if( strcmp(&argv[i][1],op[j].label)==0 ) break; + } + v = argv[i][0]=='-' ? 1 : 0; + if( op[j].label==0 ){ + if( err ){ + fprintf(err,"%sundefined option.\n",emsg); + errline(i,1,err); + } + errcnt++; + }else if( op[j].type==OPT_FLAG ){ + *((int*)op[j].arg) = v; + }else if( op[j].type==OPT_FFLAG ){ + (*(void(*)())(op[j].arg))(v); + }else{ + if( err ){ + fprintf(err,"%smissing argument on switch.\n",emsg); + errline(i,1,err); + } + errcnt++; + } + return errcnt; +} + +/* +** Process a command line switch which has an argument. +*/ +static int handleswitch(i,err) +int i; +FILE *err; +{ + int lv = 0; + double dv = 0.0; + char *sv = 0, *end; + char *cp; + int j; + int errcnt = 0; + cp = strchr(argv[i],'='); + *cp = 0; + for(j=0; op[j].label; j++){ + if( strcmp(argv[i],op[j].label)==0 ) break; + } + *cp = '='; + if( op[j].label==0 ){ + if( err ){ + fprintf(err,"%sundefined option.\n",emsg); + errline(i,0,err); + } + errcnt++; + }else{ + cp++; + switch( op[j].type ){ + case OPT_FLAG: + case OPT_FFLAG: + if( err ){ + fprintf(err,"%soption requires an argument.\n",emsg); + errline(i,0,err); + } + errcnt++; + break; + case OPT_DBL: + case OPT_FDBL: + dv = strtod(cp,&end); + if( *end ){ + if( err ){ + fprintf(err,"%sillegal character in floating-point argument.\n",emsg); + errline(i,((unsigned long)end)-(unsigned long)argv[i],err); + } + errcnt++; + } + break; + case OPT_INT: + case OPT_FINT: + lv = strtol(cp,&end,0); + if( *end ){ + if( err ){ + fprintf(err,"%sillegal character in integer argument.\n",emsg); + errline(i,((unsigned long)end)-(unsigned long)argv[i],err); + } + errcnt++; + } + break; + case OPT_STR: + case OPT_FSTR: + sv = cp; + break; + } + switch( op[j].type ){ + case OPT_FLAG: + case OPT_FFLAG: + break; + case OPT_DBL: + *(double*)(op[j].arg) = dv; + break; + case OPT_FDBL: + (*(void(*)())(op[j].arg))(dv); + break; + case OPT_INT: + *(int*)(op[j].arg) = lv; + break; + case OPT_FINT: + (*(void(*)())(op[j].arg))((int)lv); + break; + case OPT_STR: + *(char**)(op[j].arg) = sv; + break; + case OPT_FSTR: + (*(void(*)())(op[j].arg))(sv); + break; + } + } + return errcnt; +} + +int OptInit(a,o,err) +char **a; +struct s_options *o; +FILE *err; +{ + int errcnt = 0; + argv = a; + op = o; + errstream = err; + if( argv && *argv && op ){ + int i; + for(i=1; argv[i]; i++){ + if( argv[i][0]=='+' || argv[i][0]=='-' ){ + errcnt += handleflags(i,err); + }else if( strchr(argv[i],'=') ){ + errcnt += handleswitch(i,err); + } + } + } + if( errcnt>0 ){ + fprintf(err,"Valid command line options for \"%s\" are:\n",*a); + OptPrint(); + exit(1); + } + return 0; +} + +int OptNArgs(){ + int cnt = 0; + int dashdash = 0; + int i; + if( argv!=0 && argv[0]!=0 ){ + for(i=1; argv[i]; i++){ + if( dashdash || !ISOPT(argv[i]) ) cnt++; + if( strcmp(argv[i],"--")==0 ) dashdash = 1; + } + } + return cnt; +} + +char *OptArg(n) +int n; +{ + int i; + i = argindex(n); + return i>=0 ? argv[i] : 0; +} + +void OptErr(n) +int n; +{ + int i; + i = argindex(n); + if( i>=0 ) errline(i,0,errstream); +} + +void OptPrint(){ + int i; + int max, len; + max = 0; + for(i=0; op[i].label; i++){ + len = strlen(op[i].label) + 1; + switch( op[i].type ){ + case OPT_FLAG: + case OPT_FFLAG: + break; + case OPT_INT: + case OPT_FINT: + len += 9; /* length of "<integer>" */ + break; + case OPT_DBL: + case OPT_FDBL: + len += 6; /* length of "<real>" */ + break; + case OPT_STR: + case OPT_FSTR: + len += 8; /* length of "<string>" */ + break; + } + if( len>max ) max = len; + } + for(i=0; op[i].label; i++){ + switch( op[i].type ){ + case OPT_FLAG: + case OPT_FFLAG: + fprintf(errstream," -%-*s %s\n",max,op[i].label,op[i].message); + break; + case OPT_INT: + case OPT_FINT: + fprintf(errstream," %s=<integer>%*s %s\n",op[i].label, + (int)(max-strlen(op[i].label)-9),"",op[i].message); + break; + case OPT_DBL: + case OPT_FDBL: + fprintf(errstream," %s=<real>%*s %s\n",op[i].label, + (int)(max-strlen(op[i].label)-6),"",op[i].message); + break; + case OPT_STR: + case OPT_FSTR: + fprintf(errstream," %s=<string>%*s %s\n",op[i].label, + (int)(max-strlen(op[i].label)-8),"",op[i].message); + break; + } + } +} +/*********************** From the file "parse.c" ****************************/ +/* +** Input file parser for the LEMON parser generator. +*/ + +/* The state of the parser */ +struct pstate { + char *filename; /* Name of the input file */ + int tokenlineno; /* Linenumber at which current token starts */ + int errorcnt; /* Number of errors so far */ + char *tokenstart; /* Text of current token */ + struct lemon *gp; /* Global state vector */ + enum e_state { + INITIALIZE, + WAITING_FOR_DECL_OR_RULE, + WAITING_FOR_DECL_KEYWORD, + WAITING_FOR_DECL_ARG, + WAITING_FOR_PRECEDENCE_SYMBOL, + WAITING_FOR_ARROW, + IN_RHS, + LHS_ALIAS_1, + LHS_ALIAS_2, + LHS_ALIAS_3, + RHS_ALIAS_1, + RHS_ALIAS_2, + PRECEDENCE_MARK_1, + PRECEDENCE_MARK_2, + RESYNC_AFTER_RULE_ERROR, + RESYNC_AFTER_DECL_ERROR, + WAITING_FOR_DESTRUCTOR_SYMBOL, + WAITING_FOR_DATATYPE_SYMBOL, + WAITING_FOR_FALLBACK_ID + } state; /* The state of the parser */ + struct symbol *fallback; /* The fallback token */ + struct symbol *lhs; /* Left-hand side of current rule */ + char *lhsalias; /* Alias for the LHS */ + int nrhs; /* Number of right-hand side symbols seen */ + struct symbol *rhs[MAXRHS]; /* RHS symbols */ + char *alias[MAXRHS]; /* Aliases for each RHS symbol (or NULL) */ + struct rule *prevrule; /* Previous rule parsed */ + char *declkeyword; /* Keyword of a declaration */ + char **declargslot; /* Where the declaration argument should be put */ + int *decllnslot; /* Where the declaration linenumber is put */ + enum e_assoc declassoc; /* Assign this association to decl arguments */ + int preccounter; /* Assign this precedence to decl arguments */ + struct rule *firstrule; /* Pointer to first rule in the grammar */ + struct rule *lastrule; /* Pointer to the most recently parsed rule */ +}; + +/* Parse a single token */ +static void parseonetoken(psp) +struct pstate *psp; +{ + char *x; + x = Strsafe(psp->tokenstart); /* Save the token permanently */ +#if 0 + printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno, + x,psp->state); +#endif + switch( psp->state ){ + case INITIALIZE: + psp->prevrule = 0; + psp->preccounter = 0; + psp->firstrule = psp->lastrule = 0; + psp->gp->nrule = 0; + /* Fall thru to next case */ + case WAITING_FOR_DECL_OR_RULE: + if( x[0]=='%' ){ + psp->state = WAITING_FOR_DECL_KEYWORD; + }else if( islower(x[0]) ){ + psp->lhs = Symbol_new(x); + psp->nrhs = 0; + psp->lhsalias = 0; + psp->state = WAITING_FOR_ARROW; + }else if( x[0]=='{' ){ + if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"There is not prior rule opon which to attach the code \ +fragment which begins on this line."); + psp->errorcnt++; + }else if( psp->prevrule->code!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Code fragment beginning on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->line = psp->tokenlineno; + psp->prevrule->code = &x[1]; + } + }else if( x[0]=='[' ){ + psp->state = PRECEDENCE_MARK_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Token \"%s\" should be either \"%%\" or a nonterminal name.", + x); + psp->errorcnt++; + } + break; + case PRECEDENCE_MARK_1: + if( !isupper(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "The precedence symbol must be a terminal."); + psp->errorcnt++; + }else if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "There is no prior rule to assign precedence \"[%s]\".",x); + psp->errorcnt++; + }else if( psp->prevrule->precsym!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Precedence mark on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->precsym = Symbol_new(x); + } + psp->state = PRECEDENCE_MARK_2; + break; + case PRECEDENCE_MARK_2: + if( x[0]!=']' ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"]\" on precedence mark."); + psp->errorcnt++; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + break; + case WAITING_FOR_ARROW: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else if( x[0]=='(' ){ + psp->state = LHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Expected to see a \":\" following the LHS symbol \"%s\".", + psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_1: + if( isalpha(x[0]) ){ + psp->lhsalias = x; + psp->state = LHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the LHS \"%s\"\n", + x,psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = LHS_ALIAS_3; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_3: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"->\" following: \"%s(%s)\".", + psp->lhs->name,psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case IN_RHS: + if( x[0]=='.' ){ + struct rule *rp; + rp = (struct rule *)malloc( sizeof(struct rule) + + sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs ); + if( rp==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't allocate enough memory for this rule."); + psp->errorcnt++; + psp->prevrule = 0; + }else{ + int i; + rp->ruleline = psp->tokenlineno; + rp->rhs = (struct symbol**)&rp[1]; + rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]); + for(i=0; i<psp->nrhs; i++){ + rp->rhs[i] = psp->rhs[i]; + rp->rhsalias[i] = psp->alias[i]; + } + rp->lhs = psp->lhs; + rp->lhsalias = psp->lhsalias; + rp->nrhs = psp->nrhs; + rp->code = 0; + rp->precsym = 0; + rp->index = psp->gp->nrule++; + rp->nextlhs = rp->lhs->rule; + rp->lhs->rule = rp; + rp->next = 0; + if( psp->firstrule==0 ){ + psp->firstrule = psp->lastrule = rp; + }else{ + psp->lastrule->next = rp; + psp->lastrule = rp; + } + psp->prevrule = rp; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isalpha(x[0]) ){ + if( psp->nrhs>=MAXRHS ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Too many symbol on RHS or rule beginning at \"%s\".", + x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + }else{ + psp->rhs[psp->nrhs] = Symbol_new(x); + psp->alias[psp->nrhs] = 0; + psp->nrhs++; + } + }else if( x[0]=='(' && psp->nrhs>0 ){ + psp->state = RHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal character on RHS of rule: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_1: + if( isalpha(x[0]) ){ + psp->alias[psp->nrhs-1] = x; + psp->state = RHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n", + x,psp->rhs[psp->nrhs-1]->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case WAITING_FOR_DECL_KEYWORD: + if( isalpha(x[0]) ){ + psp->declkeyword = x; + psp->declargslot = 0; + psp->decllnslot = 0; + psp->state = WAITING_FOR_DECL_ARG; + if( strcmp(x,"name")==0 ){ + psp->declargslot = &(psp->gp->name); + }else if( strcmp(x,"include")==0 ){ + psp->declargslot = &(psp->gp->include); + psp->decllnslot = &psp->gp->includeln; + }else if( strcmp(x,"code")==0 ){ + psp->declargslot = &(psp->gp->extracode); + psp->decllnslot = &psp->gp->extracodeln; + }else if( strcmp(x,"token_destructor")==0 ){ + psp->declargslot = &psp->gp->tokendest; + psp->decllnslot = &psp->gp->tokendestln; + }else if( strcmp(x,"default_destructor")==0 ){ + psp->declargslot = &psp->gp->vardest; + psp->decllnslot = &psp->gp->vardestln; + }else if( strcmp(x,"token_prefix")==0 ){ + psp->declargslot = &psp->gp->tokenprefix; + }else if( strcmp(x,"syntax_error")==0 ){ + psp->declargslot = &(psp->gp->error); + psp->decllnslot = &psp->gp->errorln; + }else if( strcmp(x,"parse_accept")==0 ){ + psp->declargslot = &(psp->gp->accept); + psp->decllnslot = &psp->gp->acceptln; + }else if( strcmp(x,"parse_failure")==0 ){ + psp->declargslot = &(psp->gp->failure); + psp->decllnslot = &psp->gp->failureln; + }else if( strcmp(x,"stack_overflow")==0 ){ + psp->declargslot = &(psp->gp->overflow); + psp->decllnslot = &psp->gp->overflowln; + }else if( strcmp(x,"extra_argument")==0 ){ + psp->declargslot = &(psp->gp->arg); + }else if( strcmp(x,"token_type")==0 ){ + psp->declargslot = &(psp->gp->tokentype); + }else if( strcmp(x,"default_type")==0 ){ + psp->declargslot = &(psp->gp->vartype); + }else if( strcmp(x,"stack_size")==0 ){ + psp->declargslot = &(psp->gp->stacksize); + }else if( strcmp(x,"start_symbol")==0 ){ + psp->declargslot = &(psp->gp->start); + }else if( strcmp(x,"left")==0 ){ + psp->preccounter++; + psp->declassoc = LEFT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"right")==0 ){ + psp->preccounter++; + psp->declassoc = RIGHT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"nonassoc")==0 ){ + psp->preccounter++; + psp->declassoc = NONE; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"destructor")==0 ){ + psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL; + }else if( strcmp(x,"type")==0 ){ + psp->state = WAITING_FOR_DATATYPE_SYMBOL; + }else if( strcmp(x,"fallback")==0 ){ + psp->fallback = 0; + psp->state = WAITING_FOR_FALLBACK_ID; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Unknown declaration keyword: \"%%%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal declaration keyword: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_DESTRUCTOR_SYMBOL: + if( !isalpha(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %destructor keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_new(x); + psp->declargslot = &sp->destructor; + psp->decllnslot = &sp->destructorln; + psp->state = WAITING_FOR_DECL_ARG; + } + break; + case WAITING_FOR_DATATYPE_SYMBOL: + if( !isalpha(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %destructor keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_new(x); + psp->declargslot = &sp->datatype; + psp->decllnslot = 0; + psp->state = WAITING_FOR_DECL_ARG; + } + break; + case WAITING_FOR_PRECEDENCE_SYMBOL: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( isupper(x[0]) ){ + struct symbol *sp; + sp = Symbol_new(x); + if( sp->prec>=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol \"%s\" has already be given a precedence.",x); + psp->errorcnt++; + }else{ + sp->prec = psp->preccounter; + sp->assoc = psp->declassoc; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't assign a precedence to \"%s\".",x); + psp->errorcnt++; + } + break; + case WAITING_FOR_DECL_ARG: + if( (x[0]=='{' || x[0]=='\"' || isalnum(x[0])) ){ + if( *(psp->declargslot)!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "The argument \"%s\" to declaration \"%%%s\" is not the first.", + x[0]=='\"' ? &x[1] : x,psp->declkeyword); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + *(psp->declargslot) = (x[0]=='\"' || x[0]=='{') ? &x[1] : x; + if( psp->decllnslot ) *psp->decllnslot = psp->tokenlineno; + psp->state = WAITING_FOR_DECL_OR_RULE; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal argument to %%%s: %s",psp->declkeyword,x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_FALLBACK_ID: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( !isupper(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%fallback argument \"%s\" should be a token", x); + psp->errorcnt++; + }else{ + struct symbol *sp = Symbol_new(x); + if( psp->fallback==0 ){ + psp->fallback = sp; + }else if( sp->fallback ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "More than one fallback assigned to token %s", x); + psp->errorcnt++; + }else{ + sp->fallback = psp->fallback; + psp->gp->has_fallback = 1; + } + } + break; + case RESYNC_AFTER_RULE_ERROR: +/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; +** break; */ + case RESYNC_AFTER_DECL_ERROR: + if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; + if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD; + break; + } +} + +/* In spite of its name, this function is really a scanner. It read +** in the entire input file (all at once) then tokenizes it. Each +** token is passed to the function "parseonetoken" which builds all +** the appropriate data structures in the global state vector "gp". +*/ +void Parse(gp) +struct lemon *gp; +{ + struct pstate ps; + FILE *fp; + char *filebuf; + int filesize; + int lineno; + int c; + char *cp, *nextcp; + int startline = 0; + + ps.gp = gp; + ps.filename = gp->filename; + ps.errorcnt = 0; + ps.state = INITIALIZE; + + /* Begin by reading the input file */ + fp = fopen(ps.filename,"rb"); + if( fp==0 ){ + ErrorMsg(ps.filename,0,"Can't open this file for reading."); + gp->errorcnt++; + return; + } + fseek(fp,0,2); + filesize = ftell(fp); + rewind(fp); + filebuf = (char *)malloc( filesize+1 ); + if( filebuf==0 ){ + ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.", + filesize+1); + gp->errorcnt++; + return; + } + if( fread(filebuf,1,filesize,fp)!=filesize ){ + ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", + filesize); + free(filebuf); + gp->errorcnt++; + return; + } + fclose(fp); + filebuf[filesize] = 0; + + /* Now scan the text of the input file */ + lineno = 1; + for(cp=filebuf; (c= *cp)!=0; ){ + if( c=='\n' ) lineno++; /* Keep track of the line number */ + if( isspace(c) ){ cp++; continue; } /* Skip all white space */ + if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */ + cp+=2; + while( (c= *cp)!=0 && c!='\n' ) cp++; + continue; + } + if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */ + cp+=2; + while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c ) cp++; + continue; + } + ps.tokenstart = cp; /* Mark the beginning of the token */ + ps.tokenlineno = lineno; /* Linenumber on which token begins */ + if( c=='\"' ){ /* String literals */ + cp++; + while( (c= *cp)!=0 && c!='\"' ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c==0 ){ + ErrorMsg(ps.filename,startline, +"String starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( c=='{' ){ /* A block of C code */ + int level; + cp++; + for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){ + if( c=='\n' ) lineno++; + else if( c=='{' ) level++; + else if( c=='}' ) level--; + else if( c=='/' && cp[1]=='*' ){ /* Skip comments */ + int prevc; + cp = &cp[2]; + prevc = 0; + while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){ + if( c=='\n' ) lineno++; + prevc = c; + cp++; + } + }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */ + cp = &cp[2]; + while( (c= *cp)!=0 && c!='\n' ) cp++; + if( c ) lineno++; + }else if( c=='\'' || c=='\"' ){ /* String a character literals */ + int startchar, prevc; + startchar = c; + prevc = 0; + for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){ + if( c=='\n' ) lineno++; + if( prevc=='\\' ) prevc = 0; + else prevc = c; + } + } + } + if( c==0 ){ + ErrorMsg(ps.filename,ps.tokenlineno, +"C code starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( isalnum(c) ){ /* Identifiers */ + while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++; + nextcp = cp; + }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */ + cp += 3; + nextcp = cp; + }else{ /* All other (one character) operators */ + cp++; + nextcp = cp; + } + c = *cp; + *cp = 0; /* Null terminate the token */ + parseonetoken(&ps); /* Parse the token */ + *cp = c; /* Restore the buffer */ + cp = nextcp; + } + free(filebuf); /* Release the buffer after parsing */ + gp->rule = ps.firstrule; + gp->errorcnt = ps.errorcnt; +} +/*************************** From the file "plink.c" *********************/ +/* +** Routines processing configuration follow-set propagation links +** in the LEMON parser generator. +*/ +static struct plink *plink_freelist = 0; + +/* Allocate a new plink */ +struct plink *Plink_new(){ + struct plink *new; + + if( plink_freelist==0 ){ + int i; + int amt = 100; + plink_freelist = (struct plink *)malloc( sizeof(struct plink)*amt ); + if( plink_freelist==0 ){ + fprintf(stderr, + "Unable to allocate memory for a new follow-set propagation link.\n"); + exit(1); + } + for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1]; + plink_freelist[amt-1].next = 0; + } + new = plink_freelist; + plink_freelist = plink_freelist->next; + return new; +} + +/* Add a plink to a plink list */ +void Plink_add(plpp,cfp) +struct plink **plpp; +struct config *cfp; +{ + struct plink *new; + new = Plink_new(); + new->next = *plpp; + *plpp = new; + new->cfp = cfp; +} + +/* Transfer every plink on the list "from" to the list "to" */ +void Plink_copy(to,from) +struct plink **to; +struct plink *from; +{ + struct plink *nextpl; + while( from ){ + nextpl = from->next; + from->next = *to; + *to = from; + from = nextpl; + } +} + +/* Delete every plink on the list */ +void Plink_delete(plp) +struct plink *plp; +{ + struct plink *nextpl; + + while( plp ){ + nextpl = plp->next; + plp->next = plink_freelist; + plink_freelist = plp; + plp = nextpl; + } +} +/*********************** From the file "report.c" **************************/ +/* +** Procedures for generating reports and tables in the LEMON parser generator. +*/ + +/* Generate a filename with the given suffix. Space to hold the +** name comes from malloc() and must be freed by the calling +** function. +*/ +PRIVATE char *file_makename(lemp,suffix) +struct lemon *lemp; +char *suffix; +{ + char *name; + char *cp; + + name = malloc( strlen(lemp->filename) + strlen(suffix) + 5 ); + if( name==0 ){ + fprintf(stderr,"Can't allocate space for a filename.\n"); + exit(1); + } + /* skip directory, JK */ + if (NULL == (cp = strrchr(lemp->filename, '/'))) { + cp = lemp->filename; + } else { + cp++; + } + strcpy(name,cp); + cp = strrchr(name,'.'); + if( cp ) *cp = 0; + strcat(name,suffix); + return name; +} + +/* Open a file with a name based on the name of the input file, +** but with a different (specified) suffix, and return a pointer +** to the stream */ +PRIVATE FILE *file_open(lemp,suffix,mode) +struct lemon *lemp; +char *suffix; +char *mode; +{ + FILE *fp; + + if( lemp->outname ) free(lemp->outname); + lemp->outname = file_makename(lemp, suffix); + fp = fopen(lemp->outname,mode); + if( fp==0 && *mode=='w' ){ + fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname); + lemp->errorcnt++; + return 0; + } + return fp; +} + +/* Duplicate the input file without comments and without actions +** on rules */ +void Reprint(lemp) +struct lemon *lemp; +{ + struct rule *rp; + struct symbol *sp; + int i, j, maxlen, len, ncolumns, skip; + printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename); + maxlen = 10; + for(i=0; i<lemp->nsymbol; i++){ + sp = lemp->symbols[i]; + len = strlen(sp->name); + if( len>maxlen ) maxlen = len; + } + ncolumns = 76/(maxlen+5); + if( ncolumns<1 ) ncolumns = 1; + skip = (lemp->nsymbol + ncolumns - 1)/ncolumns; + for(i=0; i<skip; i++){ + printf("//"); + for(j=i; j<lemp->nsymbol; j+=skip){ + sp = lemp->symbols[j]; + assert( sp->index==j ); + printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name); + } + printf("\n"); + } + for(rp=lemp->rule; rp; rp=rp->next){ + printf("%s",rp->lhs->name); +/* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */ + printf(" ::="); + for(i=0; i<rp->nrhs; i++){ + printf(" %s",rp->rhs[i]->name); +/* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */ + } + printf("."); + if( rp->precsym ) printf(" [%s]",rp->precsym->name); +/* if( rp->code ) printf("\n %s",rp->code); */ + printf("\n"); + } +} + +void ConfigPrint(fp,cfp) +FILE *fp; +struct config *cfp; +{ + struct rule *rp; + int i; + rp = cfp->rp; + fprintf(fp,"%s ::=",rp->lhs->name); + for(i=0; i<=rp->nrhs; i++){ + if( i==cfp->dot ) fprintf(fp," *"); + if( i==rp->nrhs ) break; + fprintf(fp," %s",rp->rhs[i]->name); + } +} + +/* #define TEST */ +#ifdef TEST +/* Print a set */ +PRIVATE void SetPrint(out,set,lemp) +FILE *out; +char *set; +struct lemon *lemp; +{ + int i; + char *spacer; + spacer = ""; + fprintf(out,"%12s[",""); + for(i=0; i<lemp->nterminal; i++){ + if( SetFind(set,i) ){ + fprintf(out,"%s%s",spacer,lemp->symbols[i]->name); + spacer = " "; + } + } + fprintf(out,"]\n"); +} + +/* Print a plink chain */ +PRIVATE void PlinkPrint(out,plp,tag) +FILE *out; +struct plink *plp; +char *tag; +{ + while( plp ){ + fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->index); + ConfigPrint(out,plp->cfp); + fprintf(out,"\n"); + plp = plp->next; + } +} +#endif + +/* Print an action to the given file descriptor. Return FALSE if +** nothing was actually printed. +*/ +int PrintAction(struct action *ap, FILE *fp, int indent){ + int result = 1; + switch( ap->type ){ + case SHIFT: + fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->index); + break; + case REDUCE: + fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index); + break; + case ACCEPT: + fprintf(fp,"%*s accept",indent,ap->sp->name); + break; + case ERROR: + fprintf(fp,"%*s error",indent,ap->sp->name); + break; + case CONFLICT: + fprintf(fp,"%*s reduce %-3d ** Parsing conflict **", + indent,ap->sp->name,ap->x.rp->index); + break; + case SH_RESOLVED: + case RD_RESOLVED: + case NOT_USED: + result = 0; + break; + } + return result; +} + +/* Generate the "y.output" log file */ +void ReportOutput(lemp) +struct lemon *lemp; +{ + int i; + struct state *stp; + struct config *cfp; + struct action *ap; + FILE *fp; + + fp = file_open(lemp,".out","w"); + if( fp==0 ) return; + fprintf(fp," \b"); + for(i=0; i<lemp->nstate; i++){ + stp = lemp->sorted[i]; + fprintf(fp,"State %d:\n",stp->index); + if( lemp->basisflag ) cfp=stp->bp; + else cfp=stp->cfp; + while( cfp ){ + char buf[20]; + if( cfp->dot==cfp->rp->nrhs ){ + sprintf(buf,"(%d)",cfp->rp->index); + fprintf(fp," %5s ",buf); + }else{ + fprintf(fp," "); + } + ConfigPrint(fp,cfp); + fprintf(fp,"\n"); +#ifdef TEST + SetPrint(fp,cfp->fws,lemp); + PlinkPrint(fp,cfp->fplp,"To "); + PlinkPrint(fp,cfp->bplp,"From"); +#endif + if( lemp->basisflag ) cfp=cfp->bp; + else cfp=cfp->next; + } + fprintf(fp,"\n"); + for(ap=stp->ap; ap; ap=ap->next){ + if( PrintAction(ap,fp,30) ) fprintf(fp,"\n"); + } + fprintf(fp,"\n"); + } + fclose(fp); + return; +} + +/* Search for the file "name" which is in the same directory as +** the exacutable */ +PRIVATE char *pathsearch(argv0,name,modemask) +char *argv0; +char *name; +int modemask; +{ + char *pathlist; + char *path,*cp; + char c; + extern int access(); + +#ifdef __WIN32__ + cp = strrchr(argv0,'\\'); +#else + cp = strrchr(argv0,'/'); +#endif + if( cp ){ + c = *cp; + *cp = 0; + path = (char *)malloc( strlen(argv0) + strlen(name) + 2 ); + if( path ) sprintf(path,"%s/%s",argv0,name); + *cp = c; + }else{ + extern char *getenv(); + pathlist = getenv("PATH"); + if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; + path = (char *)malloc( strlen(pathlist)+strlen(name)+2 ); + if( path!=0 ){ + while( *pathlist ){ + cp = strchr(pathlist,':'); + if( cp==0 ) cp = &pathlist[strlen(pathlist)]; + c = *cp; + *cp = 0; + sprintf(path,"%s/%s",pathlist,name); + *cp = c; + if( c==0 ) pathlist = ""; + else pathlist = &cp[1]; + if( access(path,modemask)==0 ) break; + } + } + } + return path; +} + +/* Given an action, compute the integer value for that action +** which is to be put in the action table of the generated machine. +** Return negative if no action should be generated. +*/ +PRIVATE int compute_action(lemp,ap) +struct lemon *lemp; +struct action *ap; +{ + int act; + switch( ap->type ){ + case SHIFT: act = ap->x.stp->index; break; + case REDUCE: act = ap->x.rp->index + lemp->nstate; break; + case ERROR: act = lemp->nstate + lemp->nrule; break; + case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break; + default: act = -1; break; + } + return act; +} + +#define LINESIZE 1000 +/* The next cluster of routines are for reading the template file +** and writing the results to the generated parser */ +/* The first function transfers data from "in" to "out" until +** a line is seen which begins with "%%". The line number is +** tracked. +** +** if name!=0, then any word that begin with "Parse" is changed to +** begin with *name instead. +*/ +PRIVATE void tplt_xfer(name,in,out,lineno) +char *name; +FILE *in; +FILE *out; +int *lineno; +{ + int i, iStart; + char line[LINESIZE]; + while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ + (*lineno)++; + iStart = 0; + if( name ){ + for(i=0; line[i]; i++){ + if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0 + && (i==0 || !isalpha(line[i-1])) + ){ + if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]); + fprintf(out,"%s",name); + i += 4; + iStart = i+1; + } + } + } + fprintf(out,"%s",&line[iStart]); + } +} + +/* The next function finds the template file and opens it, returning +** a pointer to the opened file. */ +PRIVATE FILE *tplt_open(lemp) +struct lemon *lemp; +{ + + char buf[1000]; + FILE *in; + char *tpltname; + char *cp; + + cp = strrchr(lemp->filename,'.'); + if( cp ){ + sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename); + }else{ + sprintf(buf,"%s.lt",lemp->filename); + } + if( access(buf,004)==0 ){ + tpltname = buf; + }else if( access(lemp->tmplname,004)==0 ){ + tpltname = lemp->tmplname; + }else{ + tpltname = pathsearch(lemp->argv0,lemp->tmplname,0); + } + if( tpltname==0 ){ + fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", + lemp->tmplname); + lemp->errorcnt++; + return 0; + } + in = fopen(tpltname,"r"); + if( in==0 ){ + fprintf(stderr,"Can't open the template file \"%s\".\n",lemp->tmplname); + lemp->errorcnt++; + return 0; + } + return in; +} + +/* Print a string to the file and keep the linenumber up to date */ +PRIVATE void tplt_print(out,lemp,str,strln,lineno) +FILE *out; +struct lemon *lemp; +char *str; +int strln; +int *lineno; +{ + if( str==0 ) return; + fprintf(out,"#line %d \"%s\"\n",strln,lemp->filename); (*lineno)++; + while( *str ){ + if( *str=='\n' ) (*lineno)++; + putc(*str,out); + str++; + } + fprintf(out,"\n#line %d \"%s\"\n",*lineno+2,lemp->outname); (*lineno)+=2; + return; +} + +/* +** The following routine emits code for the destructor for the +** symbol sp +*/ +void emit_destructor_code(out,sp,lemp,lineno) +FILE *out; +struct symbol *sp; +struct lemon *lemp; +int *lineno; +{ + char *cp = 0; + + int linecnt = 0; + if( sp->type==TERMINAL ){ + cp = lemp->tokendest; + if( cp==0 ) return; + fprintf(out,"#line %d \"%s\"\n{",lemp->tokendestln,lemp->filename); + }else if( sp->destructor ){ + cp = sp->destructor; + fprintf(out,"#line %d \"%s\"\n{",sp->destructorln,lemp->filename); + }else if( lemp->vardest ){ + cp = lemp->vardest; + if( cp==0 ) return; + fprintf(out,"#line %d \"%s\"\n{",lemp->vardestln,lemp->filename); + } + for(; *cp; cp++){ + if( *cp=='$' && cp[1]=='$' ){ + fprintf(out,"(yypminor->yy%d)",sp->dtnum); + cp++; + continue; + } + if( *cp=='\n' ) linecnt++; + fputc(*cp,out); + } + (*lineno) += 3 + linecnt; + fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname); + return; +} + +/* +** Return TRUE (non-zero) if the given symbol has a destructor. +*/ +int has_destructor(sp, lemp) +struct symbol *sp; +struct lemon *lemp; +{ + int ret; + if( sp->type==TERMINAL ){ + ret = lemp->tokendest!=0; + }else{ + ret = lemp->vardest!=0 || sp->destructor!=0; + } + return ret; +} + +/* +** Generate code which executes when the rule "rp" is reduced. Write +** the code to "out". Make sure lineno stays up-to-date. +*/ +PRIVATE void emit_code(out,rp,lemp,lineno) +FILE *out; +struct rule *rp; +struct lemon *lemp; +int *lineno; +{ + char *cp, *xp; + int linecnt = 0; + int i; + char lhsused = 0; /* True if the LHS element has been used */ + char used[MAXRHS]; /* True for each RHS element which is used */ + + for(i=0; i<rp->nrhs; i++) used[i] = 0; + lhsused = 0; + + /* Generate code to do the reduce action */ + if( rp->code ){ + fprintf(out,"#line %d \"%s\"\n{",rp->line,lemp->filename); + for(cp=rp->code; *cp; cp++){ + if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){ + char saved; + for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++); + saved = *xp; + *xp = 0; + if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){ + fprintf(out,"yygotominor.yy%d",rp->lhs->dtnum); + cp = xp; + lhsused = 1; + }else{ + for(i=0; i<rp->nrhs; i++){ + if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){ + fprintf(out,"yymsp[%d].minor.yy%d",i-rp->nrhs+1,rp->rhs[i]->dtnum); + cp = xp; + used[i] = 1; + break; + } + } + } + *xp = saved; + } + if( *cp=='\n' ) linecnt++; + fputc(*cp,out); + } /* End loop */ + (*lineno) += 3 + linecnt; + fprintf(out,"}\n#line %d \"%s\"\n",*lineno,lemp->outname); + } /* End if( rp->code ) */ + + /* Check to make sure the LHS has been used */ + if( rp->lhsalias && !lhsused ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label \"%s\" for \"%s(%s)\" is never used.", + rp->lhsalias,rp->lhs->name,rp->lhsalias); + lemp->errorcnt++; + } + + /* Generate destructor code for RHS symbols which are not used in the + ** reduce code */ + for(i=0; i<rp->nrhs; i++){ + if( rp->rhsalias[i] && !used[i] ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label %s for \"%s(%s)\" is never used.", + rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]); + lemp->errorcnt++; + }else if( rp->rhsalias[i]==0 ){ + if( has_destructor(rp->rhs[i],lemp) ){ + fprintf(out," yy_destructor(%d,&yymsp[%d].minor);\n", + rp->rhs[i]->index,i-rp->nrhs+1); (*lineno)++; + }else{ + fprintf(out," /* No destructor defined for %s */\n", + rp->rhs[i]->name); + (*lineno)++; + } + } + } + return; +} + +/* +** Print the definition of the union used for the parser's data stack. +** This union contains fields for every possible data type for tokens +** and nonterminals. In the process of computing and printing this +** union, also set the ".dtnum" field of every terminal and nonterminal +** symbol. +*/ +void print_stack_union(out,lemp,plineno,mhflag) +FILE *out; /* The output stream */ +struct lemon *lemp; /* The main info structure for this parser */ +int *plineno; /* Pointer to the line number */ +int mhflag; /* True if generating makeheaders output */ +{ + int lineno = *plineno; /* The line number of the output */ + char **types; /* A hash table of datatypes */ + int arraysize; /* Size of the "types" array */ + int maxdtlength; /* Maximum length of any ".datatype" field. */ + char *stddt; /* Standardized name for a datatype */ + int i,j; /* Loop counters */ + int hash; /* For hashing the name of a type */ + char *name; /* Name of the parser */ + + /* Allocate and initialize types[] and allocate stddt[] */ + arraysize = lemp->nsymbol * 2; + types = (char**)malloc( arraysize * sizeof(char*) ); + for(i=0; i<arraysize; i++) types[i] = 0; + maxdtlength = 0; + if( lemp->vartype ){ + maxdtlength = strlen(lemp->vartype); + } + for(i=0; i<lemp->nsymbol; i++){ + int len; + struct symbol *sp = lemp->symbols[i]; + if( sp->datatype==0 ) continue; + len = strlen(sp->datatype); + if( len>maxdtlength ) maxdtlength = len; + } + stddt = (char*)malloc( maxdtlength*2 + 1 ); + if( types==0 || stddt==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + + /* Build a hash table of datatypes. The ".dtnum" field of each symbol + ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is + ** used for terminal symbols. If there is no %default_type defined then + ** 0 is also used as the .dtnum value for nonterminals which do not specify + ** a datatype using the %type directive. + */ + for(i=0; i<lemp->nsymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + char *cp; + if( sp==lemp->errsym ){ + sp->dtnum = arraysize+1; + continue; + } + if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){ + sp->dtnum = 0; + continue; + } + cp = sp->datatype; + if( cp==0 ) cp = lemp->vartype; + j = 0; + while( isspace(*cp) ) cp++; + while( *cp ) stddt[j++] = *cp++; + while( j>0 && isspace(stddt[j-1]) ) j--; + stddt[j] = 0; + hash = 0; + for(j=0; stddt[j]; j++){ + hash = hash*53 + stddt[j]; + } + hash = (hash & 0x7fffffff)%arraysize; + while( types[hash] ){ + if( strcmp(types[hash],stddt)==0 ){ + sp->dtnum = hash + 1; + break; + } + hash++; + if( hash>=arraysize ) hash = 0; + } + if( types[hash]==0 ){ + sp->dtnum = hash + 1; + types[hash] = (char*)malloc( strlen(stddt)+1 ); + if( types[hash]==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + strcpy(types[hash],stddt); + } + } + + /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */ + name = lemp->name ? lemp->name : "Parse"; + lineno = *plineno; + if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; } + fprintf(out,"#define %sTOKENTYPE %s\n",name, + lemp->tokentype?lemp->tokentype:"void*"); lineno++; + if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } + fprintf(out,"typedef union {\n"); lineno++; + fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++; + for(i=0; i<arraysize; i++){ + if( types[i]==0 ) continue; + fprintf(out," %s yy%d;\n",types[i],i+1); lineno++; + free(types[i]); + } + fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++; + free(stddt); + free(types); + fprintf(out,"} YYMINORTYPE;\n"); lineno++; + *plineno = lineno; +} + +/* +** Return the name of a C datatype able to represent values between +** lwr and upr, inclusive. +*/ +static const char *minimum_size_type(int lwr, int upr){ + if( lwr>=0 ){ + if( upr<=255 ){ + return "unsigned char"; + }else if( upr<65535 ){ + return "unsigned short int"; + }else{ + return "unsigned int"; + } + }else if( lwr>=-127 && upr<=127 ){ + return "signed char"; + }else if( lwr>=-32767 && upr<32767 ){ + return "short"; + }else{ + return "int"; + } +} + +/* +** Each state contains a set of token transaction and a set of +** nonterminal transactions. Each of these sets makes an instance +** of the following structure. An array of these structures is used +** to order the creation of entries in the yy_action[] table. +*/ +struct axset { + struct state *stp; /* A pointer to a state */ + int isTkn; /* True to use tokens. False for non-terminals */ + int nAction; /* Number of actions */ +}; + +/* +** Compare to axset structures for sorting purposes +*/ +static int axset_compare(const void *a, const void *b){ + struct axset *p1 = (struct axset*)a; + struct axset *p2 = (struct axset*)b; + return p2->nAction - p1->nAction; +} + +/* Generate C source code for the parser */ +void ReportTable(lemp, mhflag) +struct lemon *lemp; +int mhflag; /* Output in makeheaders format if true */ +{ + FILE *out, *in; + char line[LINESIZE]; + int lineno; + struct state *stp; + struct action *ap; + struct rule *rp; + struct acttab *pActtab; + int i, j, n; + char *name; + int mnTknOfst, mxTknOfst; + int mnNtOfst, mxNtOfst; + struct axset *ax; + + in = tplt_open(lemp); + if( in==0 ) return; + out = file_open(lemp,".c","w"); + if( out==0 ){ + fclose(in); + return; + } + lineno = 1; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the include code, if any */ + tplt_print(out,lemp,lemp->include,lemp->includeln,&lineno); + if( mhflag ){ + char *name = file_makename(lemp, ".h"); + fprintf(out,"#include \"%s\"\n", name); lineno++; + free(name); + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate #defines for all tokens */ + if( mhflag ){ + char *prefix; + fprintf(out,"#if INTERFACE\n"); lineno++; + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + for(i=1; i<lemp->nterminal; i++){ + fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + lineno++; + } + fprintf(out,"#endif\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the defines */ + fprintf(out,"/* \001 */\n"); + fprintf(out,"#define YYCODETYPE %s\n", + minimum_size_type(0, lemp->nsymbol+5)); lineno++; + fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; + fprintf(out,"#define YYACTIONTYPE %s\n", + minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++; + print_stack_union(out,lemp,&lineno,mhflag); + if( lemp->stacksize ){ + if( atoi(lemp->stacksize)<=0 ){ + ErrorMsg(lemp->filename,0, +"Illegal stack size: [%s]. The stack size should be an integer constant.", + lemp->stacksize); + lemp->errorcnt++; + lemp->stacksize = "100"; + } + fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++; + }else{ + fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++; + } + if( mhflag ){ + fprintf(out,"#if INTERFACE\n"); lineno++; + } + name = lemp->name ? lemp->name : "Parse"; + if( lemp->arg && lemp->arg[0] ){ + int i; + i = strlen(lemp->arg); + while( i>=1 && isspace(lemp->arg[i-1]) ) i--; + while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--; + fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n", + name,lemp->arg,&lemp->arg[i]); lineno++; + fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n", + name,&lemp->arg[i],&lemp->arg[i]); lineno++; + }else{ + fprintf(out,"#define %sARG_SDECL\n",name); lineno++; + fprintf(out,"#define %sARG_PDECL\n",name); lineno++; + fprintf(out,"#define %sARG_FETCH\n",name); lineno++; + fprintf(out,"#define %sARG_STORE\n",name); lineno++; + } + if( mhflag ){ + fprintf(out,"#endif\n"); lineno++; + } + fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++; + fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; + fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; + fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; + if( lemp->has_fallback ){ + fprintf(out,"#define YYFALLBACK 1\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the action table and its associates: + ** + ** yy_action[] A single table containing all actions. + ** yy_lookahead[] A table containing the lookahead for each entry in + ** yy_action. Used to detect hash collisions. + ** yy_shift_ofst[] For each state, the offset into yy_action for + ** shifting terminals. + ** yy_reduce_ofst[] For each state, the offset into yy_action for + ** shifting non-terminals after a reduce. + ** yy_default[] Default action for each state. + */ + + /* Compute the actions on all states and count them up */ + ax = malloc( sizeof(ax[0])*lemp->nstate*2 ); + if( ax==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=0; i<lemp->nstate; i++){ + stp = lemp->sorted[i]; + stp->nTknAct = stp->nNtAct = 0; + stp->iDflt = lemp->nstate + lemp->nrule; + stp->iTknOfst = NO_OFFSET; + stp->iNtOfst = NO_OFFSET; + for(ap=stp->ap; ap; ap=ap->next){ + if( compute_action(lemp,ap)>=0 ){ + if( ap->sp->index<lemp->nterminal ){ + stp->nTknAct++; + }else if( ap->sp->index<lemp->nsymbol ){ + stp->nNtAct++; + }else{ + stp->iDflt = compute_action(lemp, ap); + } + } + } + ax[i*2].stp = stp; + ax[i*2].isTkn = 1; + ax[i*2].nAction = stp->nTknAct; + ax[i*2+1].stp = stp; + ax[i*2+1].isTkn = 0; + ax[i*2+1].nAction = stp->nNtAct; + } + mxTknOfst = mnTknOfst = 0; + mxNtOfst = mnNtOfst = 0; + + /* Compute the action table. In order to try to keep the size of the + ** action table to a minimum, the heuristic of placing the largest action + ** sets first is used. + */ + qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare); + pActtab = acttab_alloc(); + for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++){ + stp = ax[i].stp; + if( ax[i].isTkn ){ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->index>=lemp->nterminal ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iTknOfst = acttab_insert(pActtab); + if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst; + if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst; + }else{ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->index<lemp->nterminal ) continue; + if( ap->sp->index==lemp->nsymbol ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iNtOfst = acttab_insert(pActtab); + if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst; + if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; + } + } + free(ax); + + /* Output the yy_action table */ + fprintf(out,"static YYACTIONTYPE yy_action[] = {\n"); lineno++; + n = acttab_size(pActtab); + for(i=j=0; i<n; i++){ + int action = acttab_yyaction(pActtab, i); + if( action<0 ) action = lemp->nsymbol + lemp->nrule + 2; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", action); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_lookahead table */ + fprintf(out,"static YYCODETYPE yy_lookahead[] = {\n"); lineno++; + for(i=j=0; i<n; i++){ + int la = acttab_yylookahead(pActtab, i); + if( la<0 ) la = lemp->nsymbol; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", la); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_shift_ofst[] table */ + fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++; + fprintf(out, "static %s yy_shift_ofst[] = {\n", + minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++; + n = lemp->nstate; + for(i=j=0; i<n; i++){ + int ofst; + stp = lemp->sorted[i]; + ofst = stp->iTknOfst; + if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_reduce_ofst[] table */ + fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; + fprintf(out, "static %s yy_reduce_ofst[] = {\n", + minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++; + n = lemp->nstate; + for(i=j=0; i<n; i++){ + int ofst; + stp = lemp->sorted[i]; + ofst = stp->iNtOfst; + if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the default action table */ + fprintf(out, "static YYACTIONTYPE yy_default[] = {\n"); lineno++; + n = lemp->nstate; + for(i=j=0; i<n; i++){ + stp = lemp->sorted[i]; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", stp->iDflt); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of fallback tokens. + */ + if( lemp->has_fallback ){ + for(i=0; i<lemp->nterminal; i++){ + struct symbol *p = lemp->symbols[i]; + if( p->fallback==0 ){ + fprintf(out, " 0, /* %10s => nothing */\n", p->name); + }else{ + fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index, + p->name, p->fallback->name); + } + lineno++; + } + } + tplt_xfer(lemp->name, in, out, &lineno); + + /* Generate a table containing the symbolic name of every symbol + */ + for(i=0; i<lemp->nsymbol; i++){ + sprintf(line,"\"%s\",",lemp->symbols[i]->name); + fprintf(out," %-15s",line); + if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; } + } + if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate a table containing a text string that describes every + ** rule in the rule set of the grammer. This information is used + ** when tracing REDUCE actions. + */ + for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ + assert( rp->index==i ); + fprintf(out," /* %3d */ \"%s ::=", i, rp->lhs->name); + for(j=0; j<rp->nrhs; j++) fprintf(out," %s",rp->rhs[j]->name); + fprintf(out,"\",\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes every time a symbol is popped from + ** the stack while processing errors or while destroying the parser. + ** (In other words, generate the %destructor actions) + */ + if( lemp->tokendest ){ + for(i=0; i<lemp->nsymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type!=TERMINAL ) continue; + fprintf(out," case %d:\n",sp->index); lineno++; + } + for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++); + if( i<lemp->nsymbol ){ + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + } + for(i=0; i<lemp->nsymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue; + fprintf(out," case %d:\n",sp->index); lineno++; + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + if( lemp->vardest ){ + struct symbol *dflt_sp = 0; + for(i=0; i<lemp->nsymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || + sp->index<=0 || sp->destructor!=0 ) continue; + fprintf(out," case %d:\n",sp->index); lineno++; + dflt_sp = sp; + } + if( dflt_sp!=0 ){ + emit_destructor_code(out,dflt_sp,lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes whenever the parser stack overflows */ + tplt_print(out,lemp,lemp->overflow,lemp->overflowln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of rule information + ** + ** Note: This code depends on the fact that rules are number + ** sequentually beginning with 0. + */ + for(rp=lemp->rule; rp; rp=rp->next){ + fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which execution during each REDUCE action */ + for(rp=lemp->rule; rp; rp=rp->next){ + fprintf(out," case %d:\n",rp->index); lineno++; + emit_code(out,rp,lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes if a parse fails */ + tplt_print(out,lemp,lemp->failure,lemp->failureln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when a syntax error occurs */ + tplt_print(out,lemp,lemp->error,lemp->errorln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when the parser accepts its input */ + tplt_print(out,lemp,lemp->accept,lemp->acceptln,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Append any addition code the user desires */ + tplt_print(out,lemp,lemp->extracode,lemp->extracodeln,&lineno); + + fclose(in); + fclose(out); + return; +} + +/* Generate a header file for the parser */ +void ReportHeader(lemp) +struct lemon *lemp; +{ + FILE *out, *in; + char *prefix; + char line[LINESIZE]; + char pattern[LINESIZE]; + int i; + + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + in = file_open(lemp,".h","r"); + if( in ){ + for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){ + sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + if( strcmp(line,pattern) ) break; + } + fclose(in); + if( i==lemp->nterminal ){ + /* No change in the file. Don't rewrite it. */ + return; + } + } + out = file_open(lemp,".h","w"); + if( out ){ + for(i=1; i<lemp->nterminal; i++){ + fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + } + fclose(out); + } + return; +} + +/* Reduce the size of the action tables, if possible, by making use +** of defaults. +** +** In this version, we take the most frequent REDUCE action and make +** it the default. Only default a reduce if there are more than one. +*/ +void CompressTables(lemp) +struct lemon *lemp; +{ + struct state *stp; + struct action *ap, *ap2; + struct rule *rp, *rp2, *rbest; + int nbest, n; + int i; + + for(i=0; i<lemp->nstate; i++){ + stp = lemp->sorted[i]; + nbest = 0; + rbest = 0; + + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type!=REDUCE ) continue; + rp = ap->x.rp; + if( rp==rbest ) continue; + n = 1; + for(ap2=ap->next; ap2; ap2=ap2->next){ + if( ap2->type!=REDUCE ) continue; + rp2 = ap2->x.rp; + if( rp2==rbest ) continue; + if( rp2==rp ) n++; + } + if( n>nbest ){ + nbest = n; + rbest = rp; + } + } + + /* Do not make a default if the number of rules to default + ** is not at least 2 */ + if( nbest<2 ) continue; + + + /* Combine matching REDUCE actions into a single default */ + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) break; + } + assert( ap ); + ap->sp = Symbol_new("{default}"); + for(ap=ap->next; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED; + } + stp->ap = Action_sort(stp->ap); + } +} + +/***************** From the file "set.c" ************************************/ +/* +** Set manipulation routines for the LEMON parser generator. +*/ + +static int size = 0; + +/* Set the set size */ +void SetSize(n) +int n; +{ + size = n+1; +} + +/* Allocate a new set */ +char *SetNew(){ + char *s; + int i; + s = (char*)malloc( size ); + if( s==0 ){ + extern void memory_error(); + memory_error(); + } + for(i=0; i<size; i++) s[i] = 0; + return s; +} + +/* Deallocate a set */ +void SetFree(s) +char *s; +{ + free(s); +} + +/* Add a new element to the set. Return TRUE if the element was added +** and FALSE if it was already there. */ +int SetAdd(s,e) +char *s; +int e; +{ + int rv; + rv = s[e]; + s[e] = 1; + return !rv; +} + +/* Add every element of s2 to s1. Return TRUE if s1 changes. */ +int SetUnion(s1,s2) +char *s1; +char *s2; +{ + int i, progress; + progress = 0; + for(i=0; i<size; i++){ + if( s2[i]==0 ) continue; + if( s1[i]==0 ){ + progress = 1; + s1[i] = 1; + } + } + return progress; +} +/********************** From the file "table.c" ****************************/ +/* +** All code in this file has been automatically generated +** from a specification in the file +** "table.q" +** by the associative array code building program "aagen". +** Do not edit this file! Instead, edit the specification +** file, then rerun aagen. +*/ +/* +** Code for processing tables in the LEMON parser generator. +*/ + +PRIVATE int strhash(x) +char *x; +{ + int h = 0; + while( *x) h = h*13 + *(x++); + return h; +} + +/* Works like strdup, sort of. Save a string in malloced memory, but +** keep strings in a table so that the same string is not in more +** than one place. +*/ +char *Strsafe(y) +char *y; +{ + char *z; + + z = Strsafe_find(y); + if( z==0 && (z=malloc( strlen(y)+1 ))!=0 ){ + strcpy(z,y); + Strsafe_insert(z); + } + MemoryCheck(z); + return z; +} + +/* There is one instance of the following structure for each +** associative array of type "x1". +*/ +struct s_x1 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x1node *tbl; /* The data stored here */ + struct s_x1node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x1". +*/ +typedef struct s_x1node { + char *data; /* The data */ + struct s_x1node *next; /* Next entry with the same hash */ + struct s_x1node **from; /* Previous link */ +} x1node; + +/* There is only one instance of the array, which is the following */ +static struct s_x1 *x1a; + +/* Allocate a new associative array */ +void Strsafe_init(){ + if( x1a ) return; + x1a = (struct s_x1*)malloc( sizeof(struct s_x1) ); + if( x1a ){ + x1a->size = 1024; + x1a->count = 0; + x1a->tbl = (x1node*)malloc( + (sizeof(x1node) + sizeof(x1node*))*1024 ); + if( x1a->tbl==0 ){ + free(x1a); + x1a = 0; + }else{ + int i; + x1a->ht = (x1node**)&(x1a->tbl[1024]); + for(i=0; i<1024; i++) x1a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Strsafe_insert(data) +char *data; +{ + x1node *np; + int h; + int ph; + + if( x1a==0 ) return 0; + ph = strhash(data); + h = ph & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x1a->count>=x1a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x1 array; + array.size = size = x1a->size*2; + array.count = x1a->count; + array.tbl = (x1node*)malloc( + (sizeof(x1node) + sizeof(x1node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x1node**)&(array.tbl[size]); + for(i=0; i<size; i++) array.ht[i] = 0; + for(i=0; i<x1a->count; i++){ + x1node *oldnp, *newnp; + oldnp = &(x1a->tbl[i]); + h = strhash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x1a->tbl); + *x1a = array; + } + /* Insert the new data */ + h = ph & (x1a->size-1); + np = &(x1a->tbl[x1a->count++]); + np->data = data; + if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next); + np->next = x1a->ht[h]; + x1a->ht[h] = np; + np->from = &(x1a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +char *Strsafe_find(key) +char *key; +{ + int h; + x1node *np; + + if( x1a==0 ) return 0; + h = strhash(key) & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return a pointer to the (terminal or nonterminal) symbol "x". +** Create a new symbol if this is the first time "x" has been seen. +*/ +struct symbol *Symbol_new(x) +char *x; +{ + struct symbol *sp; + + sp = Symbol_find(x); + if( sp==0 ){ + sp = (struct symbol *)malloc( sizeof(struct symbol) ); + MemoryCheck(sp); + sp->name = Strsafe(x); + sp->type = isupper(*x) ? TERMINAL : NONTERMINAL; + sp->rule = 0; + sp->fallback = 0; + sp->prec = -1; + sp->assoc = UNK; + sp->firstset = 0; + sp->lambda = B_FALSE; + sp->destructor = 0; + sp->datatype = 0; + Symbol_insert(sp,sp->name); + } + return sp; +} + +/* Compare two symbols for working purposes +** +** Symbols that begin with upper case letters (terminals or tokens) +** must sort before symbols that begin with lower case letters +** (non-terminals). Other than that, the order does not matter. +** +** We find experimentally that leaving the symbols in their original +** order (the order they appeared in the grammar file) gives the +** smallest parser tables in SQLite. +*/ +int Symbolcmpp(struct symbol **a, struct symbol **b){ + int i1 = (**a).index + 10000000*((**a).name[0]>'Z'); + int i2 = (**b).index + 10000000*((**b).name[0]>'Z'); + return i1-i2; +} + +/* There is one instance of the following structure for each +** associative array of type "x2". +*/ +struct s_x2 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x2node *tbl; /* The data stored here */ + struct s_x2node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x2". +*/ +typedef struct s_x2node { + struct symbol *data; /* The data */ + char *key; /* The key */ + struct s_x2node *next; /* Next entry with the same hash */ + struct s_x2node **from; /* Previous link */ +} x2node; + +/* There is only one instance of the array, which is the following */ +static struct s_x2 *x2a; + +/* Allocate a new associative array */ +void Symbol_init(){ + if( x2a ) return; + x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); + if( x2a ){ + x2a->size = 128; + x2a->count = 0; + x2a->tbl = (x2node*)malloc( + (sizeof(x2node) + sizeof(x2node*))*128 ); + if( x2a->tbl==0 ){ + free(x2a); + x2a = 0; + }else{ + int i; + x2a->ht = (x2node**)&(x2a->tbl[128]); + for(i=0; i<128; i++) x2a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Symbol_insert(data,key) +struct symbol *data; +char *key; +{ + x2node *np; + int h; + int ph; + + if( x2a==0 ) return 0; + ph = strhash(key); + h = ph & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x2a->count>=x2a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x2 array; + array.size = size = x2a->size*2; + array.count = x2a->count; + array.tbl = (x2node*)malloc( + (sizeof(x2node) + sizeof(x2node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x2node**)&(array.tbl[size]); + for(i=0; i<size; i++) array.ht[i] = 0; + for(i=0; i<x2a->count; i++){ + x2node *oldnp, *newnp; + oldnp = &(x2a->tbl[i]); + h = strhash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x2a->tbl); + *x2a = array; + } + /* Insert the new data */ + h = ph & (x2a->size-1); + np = &(x2a->tbl[x2a->count++]); + np->key = key; + np->data = data; + if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next); + np->next = x2a->ht[h]; + x2a->ht[h] = np; + np->from = &(x2a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct symbol *Symbol_find(key) +char *key; +{ + int h; + x2node *np; + + if( x2a==0 ) return 0; + h = strhash(key) & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return the n-th data. Return NULL if n is out of range. */ +struct symbol *Symbol_Nth(n) +int n; +{ + struct symbol *data; + if( x2a && n>0 && n<=x2a->count ){ + data = x2a->tbl[n-1].data; + }else{ + data = 0; + } + return data; +} + +/* Return the size of the array */ +int Symbol_count() +{ + return x2a ? x2a->count : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct symbol **Symbol_arrayof() +{ + struct symbol **array; + int i,size; + if( x2a==0 ) return 0; + size = x2a->count; + array = (struct symbol **)malloc( sizeof(struct symbol *)*size ); + if( array ){ + for(i=0; i<size; i++) array[i] = x2a->tbl[i].data; + } + return array; +} + +/* Compare two configurations */ +int Configcmp(a,b) +struct config *a; +struct config *b; +{ + int x; + x = a->rp->index - b->rp->index; + if( x==0 ) x = a->dot - b->dot; + return x; +} + +/* Compare two states */ +PRIVATE int statecmp(a,b) +struct config *a; +struct config *b; +{ + int rc; + for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){ + rc = a->rp->index - b->rp->index; + if( rc==0 ) rc = a->dot - b->dot; + } + if( rc==0 ){ + if( a ) rc = 1; + if( b ) rc = -1; + } + return rc; +} + +/* Hash a state */ +PRIVATE int statehash(a) +struct config *a; +{ + int h=0; + while( a ){ + h = h*571 + a->rp->index*37 + a->dot; + a = a->bp; + } + return h; +} + +/* Allocate a new state structure */ +struct state *State_new() +{ + struct state *new; + new = (struct state *)malloc( sizeof(struct state) ); + MemoryCheck(new); + return new; +} + +/* There is one instance of the following structure for each +** associative array of type "x3". +*/ +struct s_x3 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x3node *tbl; /* The data stored here */ + struct s_x3node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x3". +*/ +typedef struct s_x3node { + struct state *data; /* The data */ + struct config *key; /* The key */ + struct s_x3node *next; /* Next entry with the same hash */ + struct s_x3node **from; /* Previous link */ +} x3node; + +/* There is only one instance of the array, which is the following */ +static struct s_x3 *x3a; + +/* Allocate a new associative array */ +void State_init(){ + if( x3a ) return; + x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); + if( x3a ){ + x3a->size = 128; + x3a->count = 0; + x3a->tbl = (x3node*)malloc( + (sizeof(x3node) + sizeof(x3node*))*128 ); + if( x3a->tbl==0 ){ + free(x3a); + x3a = 0; + }else{ + int i; + x3a->ht = (x3node**)&(x3a->tbl[128]); + for(i=0; i<128; i++) x3a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int State_insert(data,key) +struct state *data; +struct config *key; +{ + x3node *np; + int h; + int ph; + + if( x3a==0 ) return 0; + ph = statehash(key); + h = ph & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x3a->count>=x3a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x3 array; + array.size = size = x3a->size*2; + array.count = x3a->count; + array.tbl = (x3node*)malloc( + (sizeof(x3node) + sizeof(x3node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x3node**)&(array.tbl[size]); + for(i=0; i<size; i++) array.ht[i] = 0; + for(i=0; i<x3a->count; i++){ + x3node *oldnp, *newnp; + oldnp = &(x3a->tbl[i]); + h = statehash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x3a->tbl); + *x3a = array; + } + /* Insert the new data */ + h = ph & (x3a->size-1); + np = &(x3a->tbl[x3a->count++]); + np->key = key; + np->data = data; + if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next); + np->next = x3a->ht[h]; + x3a->ht[h] = np; + np->from = &(x3a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct state *State_find(key) +struct config *key; +{ + int h; + x3node *np; + + if( x3a==0 ) return 0; + h = statehash(key) & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct state **State_arrayof() +{ + struct state **array; + int i,size; + if( x3a==0 ) return 0; + size = x3a->count; + array = (struct state **)malloc( sizeof(struct state *)*size ); + if( array ){ + for(i=0; i<size; i++) array[i] = x3a->tbl[i].data; + } + return array; +} + +/* Hash a configuration */ +PRIVATE int confighash(a) +struct config *a; +{ + int h=0; + h = h*571 + a->rp->index*37 + a->dot; + return h; +} + +/* There is one instance of the following structure for each +** associative array of type "x4". +*/ +struct s_x4 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x4node *tbl; /* The data stored here */ + struct s_x4node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x4". +*/ +typedef struct s_x4node { + struct config *data; /* The data */ + struct s_x4node *next; /* Next entry with the same hash */ + struct s_x4node **from; /* Previous link */ +} x4node; + +/* There is only one instance of the array, which is the following */ +static struct s_x4 *x4a; + +/* Allocate a new associative array */ +void Configtable_init(){ + if( x4a ) return; + x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); + if( x4a ){ + x4a->size = 64; + x4a->count = 0; + x4a->tbl = (x4node*)malloc( + (sizeof(x4node) + sizeof(x4node*))*64 ); + if( x4a->tbl==0 ){ + free(x4a); + x4a = 0; + }else{ + int i; + x4a->ht = (x4node**)&(x4a->tbl[64]); + for(i=0; i<64; i++) x4a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Configtable_insert(data) +struct config *data; +{ + x4node *np; + int h; + int ph; + + if( x4a==0 ) return 0; + ph = confighash(data); + h = ph & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp(np->data,data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x4a->count>=x4a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x4 array; + array.size = size = x4a->size*2; + array.count = x4a->count; + array.tbl = (x4node*)malloc( + (sizeof(x4node) + sizeof(x4node*))*size ); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x4node**)&(array.tbl[size]); + for(i=0; i<size; i++) array.ht[i] = 0; + for(i=0; i<x4a->count; i++){ + x4node *oldnp, *newnp; + oldnp = &(x4a->tbl[i]); + h = confighash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x4a->tbl); + *x4a = array; + } + /* Insert the new data */ + h = ph & (x4a->size-1); + np = &(x4a->tbl[x4a->count++]); + np->data = data; + if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next); + np->next = x4a->ht[h]; + x4a->ht[h] = np; + np->from = &(x4a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct config *Configtable_find(key) +struct config *key; +{ + int h; + x4node *np; + + if( x4a==0 ) return 0; + h = confighash(key) & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp(np->data,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Remove all data from the table. Pass each data to the function "f" +** as it is removed. ("f" may be null to avoid this step.) */ +void Configtable_clear(f) +int(*f)(/* struct config * */); +{ + int i; + if( x4a==0 || x4a->count==0 ) return; + if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data); + for(i=0; i<x4a->size; i++) x4a->ht[i] = 0; + x4a->count = 0; + return; +} diff --git a/src/lempar.c b/src/lempar.c new file mode 100644 index 00000000..ee1edbfa --- /dev/null +++ b/src/lempar.c @@ -0,0 +1,687 @@ +/* Driver template for the LEMON parser generator. +** The author disclaims copyright to this source code. +*/ +/* First off, code is include which follows the "include" declaration +** in the input file. */ +#include <stdio.h> +%% +/* Next is all token values, in a form suitable for use by makeheaders. +** This section will be null unless lemon is run with the -m switch. +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ +%% +/* Make sure the INTERFACE macro is defined. +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** YYCODETYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 terminals +** and nonterminals. "int" is used otherwise. +** YYNOCODE is a number of type YYCODETYPE which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** YYACTIONTYPE is the data type used for storing terminal +** and nonterminal numbers. "unsigned char" is +** used if there are fewer than 250 rules and +** states combined. "int" is used otherwise. +** ParseTOKENTYPE is the data type used for minor tokens given +** directly to the parser from the tokenizer. +** YYMINORTYPE is the data type used for all minor tokens. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for base tokens is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ +%% +#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) +#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) +#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** +** N == YYNSTATE+YYNRULE A syntax error has occurred. +** +** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** +** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as +** +** yy_action[ yy_shift_ofst[S] + X ] +** +** If the index value yy_shift_ofst[S]+X is out of range or if the value +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] +** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table +** and that yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +*/ +%% +#define YY_SZ_ACTTAB (sizeof(yy_action)/sizeof(yy_action[0])) + +/* The next table maps tokens into fallback tokens. If a construct +** like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammer, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +%% +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +*/ +struct yyStackEntry { + int stateno; /* The state-number */ + int major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + int yyidx; /* Index of top element in stack */ + int yyerrcnt; /* Shifts left before out of the error */ + ParseARG_SDECL /* A place to hold %extra_argument */ + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include <stdio.h> +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +** <ul> +** <li> A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +** <li> A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +** </ul> +** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *yyTokenName[] = { +%% +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *yyRuleName[] = { +%% +}; +#endif /* NDEBUG */ + +/* +** This function returns the symbolic name associated with a token +** value. +*/ +const char *ParseTokenName(int tokenType){ +#ifndef NDEBUG + if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){ + return yyTokenName[tokenType]; + }else{ + return "Unknown"; + } +#else + return ""; +#endif +} + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(*mallocProc)(size_t)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); + if( pParser ){ + pParser->yyidx = -1; + } + return pParser; +} + +/* The following function deletes the value associated with a +** symbol. The symbol can be either a terminal or nonterminal. +** "yymajor" is the symbol code, and "yypminor" is a pointer to +** the value. +*/ +static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){ + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ +%% + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +** +** Return the major token number for the symbol popped. +*/ +static int yy_pop_parser_stack(yyParser *pParser){ + YYCODETYPE yymajor; + yyStackEntry *yytos = &pParser->yystack[pParser->yyidx]; + + if( pParser->yyidx<0 ) return 0; +#ifndef NDEBUG + if( yyTraceFILE && pParser->yyidx>=0 ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yymajor = yytos->major; + yy_destructor( yymajor, &yytos->minor); + pParser->yyidx--; + return yymajor; +} + +/* +** Deallocate and destroy a parser. Destructors are all called for +** all stack elements before shutting the parser down. +** +** Inputs: +** <ul> +** <li> A pointer to the parser. This should be a pointer +** obtained from ParseAlloc. +** <li> A pointer to a function used to reclaim memory obtained +** from malloc. +** </ul> +*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; + if( pParser==0 ) return; + while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser); + (*freeProc)((void*)pParser); +} + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_shift_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + /* if( pParser->yyidx<0 ) return YY_NO_ACTION; */ + i = yy_shift_ofst[stateno]; + if( i==YY_SHIFT_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + int iFallback; /* Fallback token */ + if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) + && (iFallback = yyFallback[iLookAhead])!=0 ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + return yy_find_shift_action(pParser, iFallback); + } +#endif + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +** +** If the look-ahead token is YYNOCODE, then check to see if the action is +** independent of the look-ahead. If it is, return the action, otherwise +** return YY_NO_ACTION. +*/ +static int yy_find_reduce_action( + yyParser *pParser, /* The parser */ + int iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yystack[pParser->yyidx].stateno; + + i = yy_reduce_ofst[stateno]; + if( i==YY_REDUCE_USE_DFLT ){ + return yy_default[stateno]; + } + if( iLookAhead==YYNOCODE ){ + return YY_NO_ACTION; + } + i += iLookAhead; + if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + }else{ + return yy_action[i]; + } +} + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yyidx++; + if( yypParser->yyidx>=YYSTACKDEPTH ){ + ParseARG_FETCH; + yypParser->yyidx--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ + return; + } + yytos = &yypParser->yystack[yypParser->yyidx]; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor = *yypMinor; +#ifndef NDEBUG + if( yyTraceFILE && yypParser->yyidx>0 ){ + int i; + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + } +#endif +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { +%% +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + YYMINORTYPE yygotominor; /* The LHS of the rule reduced */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + yymsp = &yypParser->yystack[yypParser->yyidx]; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno>=0 + && yyruleno<sizeof(yyRuleName)/sizeof(yyRuleName[0]) ){ + fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, + yyRuleName[yyruleno]); + } +#endif /* NDEBUG */ + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line <lineno> <grammarfile> + ** { ... } // User supplied code + ** #line <lineno> <thisfile> + ** break; + */ +%% + }; + yygoto = yyRuleInfo[yyruleno].lhs; + yysize = yyRuleInfo[yyruleno].nrhs; + yypParser->yyidx -= yysize; + yyact = yy_find_reduce_action(yypParser,yygoto); + if( yyact < YYNSTATE ){ + yy_shift(yypParser,yyact,yygoto,&yygotominor); + }else if( yyact == YYNSTATE + YYNRULE + 1 ){ + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + YYMINORTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN (yyminor.yy0) +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif + while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +%% + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +** <ul> +** <li> A pointer to the parser (an opaque structure.) +** <li> The major token number. +** <li> The minor token number. +** <li> An option argument of a grammar-specified type. +** </ul> +** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + int yyact; /* The parser action. */ + int yyendofinput; /* True if we are at the end of input */ + int yyerrorhit = 0; /* True if yymajor has invoked an error */ + yyParser *yypParser; /* The parser */ + + /* (re)initialize the parser, if necessary */ + yypParser = (yyParser*)yyp; + if( yypParser->yyidx<0 ){ + if( yymajor==0 ) return; + yypParser->yyidx = 0; + yypParser->yyerrcnt = -1; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; + } + yyminorunion.yy0 = yyminor; + yyendofinput = (yymajor==0); + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,yymajor); + if( yyact<YYNSTATE ){ + yy_shift(yypParser,yyact,yymajor,&yyminorunion); + yypParser->yyerrcnt--; + if( yyendofinput && yypParser->yyidx>=0 ){ + yymajor = 0; + }else{ + yymajor = YYNOCODE; + } + }else if( yyact < YYNSTATE + YYNRULE ){ + yy_reduce(yypParser,yyact-YYNSTATE); + }else if( yyact == YY_ERROR_ACTION ){ + int yymx; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yymx = yypParser->yystack[yypParser->yyidx].major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yymajor,&yyminorunion); + yymajor = YYNOCODE; + }else{ + while( + yypParser->yyidx >= 0 && + yymx != YYERRORSYMBOL && + (yyact = yy_find_shift_action(yypParser,YYERRORSYMBOL)) >= YYNSTATE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yyidx < 0 || yymajor==0 ){ + yy_destructor(yymajor,&yyminorunion); + yy_parse_failed(yypParser); + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + YYMINORTYPE u2; + u2.YYERRSYMDT = 0; + yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor,yyminorunion); + } + yypParser->yyerrcnt = 3; + yy_destructor(yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); + } + yymajor = YYNOCODE; +#endif + }else{ + yy_accept(yypParser); + yymajor = YYNOCODE; + } + }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); + return; +} diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..7d7a3331 --- /dev/null +++ b/src/log.c @@ -0,0 +1,238 @@ +#define _GNU_SOURCE + +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> + +#include <stdarg.h> +#include <stdio.h> + +#include "config.h" + +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#endif + +#include "log.h" +#include "array.h" + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif + +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif + +/** + * open the errorlog + * + * if the open failed, report to the user and die + * if no filename is given, use syslog instead + * + */ + +int log_error_open(server *srv) { + int fd; + int close_stderr = 1; + + if (srv->srvconf.error_logfile->used) { + const char *logfile = srv->srvconf.error_logfile->ptr; + + if (-1 == (srv->log_error_fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) { + log_error_write(srv, __FILE__, __LINE__, "SSSS", + "opening errorlog '", logfile, + "' failed: ", strerror(errno)); + + return -1; + } +#ifdef FD_CLOEXEC + /* close fd on exec (cgi) */ + fcntl(srv->log_error_fd, F_SETFD, FD_CLOEXEC); +#endif + } else { + srv->log_error_fd = -1; +#ifdef HAVE_SYSLOG_H + /* syslog-mode */ + srv->log_using_syslog = 1; + openlog("lighttpd", LOG_CONS, LOG_LOCAL0); +#endif + } + + log_error_write(srv, __FILE__, __LINE__, "s", "server started"); + +#ifdef HAVE_VALGRIND_VALGRIND_H + /* don't close stderr for debugging purposes if run in valgrind */ + if (RUNNING_ON_VALGRIND) close_stderr = 0; +#endif + + /* move stderr to /dev/null */ + if (close_stderr && + -1 != (fd = open("/dev/null", O_WRONLY))) { + close(STDERR_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + return 0; +} + +/** + * open the errorlog + * + * if the open failed, report to the user and die + * if no filename is given, use syslog instead + * + */ + +int log_error_cycle(server *srv) { + /* only cycle if we are not in syslog-mode */ + + if (0 == srv->log_using_syslog) { + const char *logfile = srv->srvconf.error_logfile->ptr; + /* already check of opening time */ + + int new_fd; + + if (-1 == (new_fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) { + /* write to old log */ + log_error_write(srv, __FILE__, __LINE__, "SSSSS", + "cycling errorlog '", logfile, + "' failed: ", strerror(errno), + ", falling back to syslog()"); + + close(srv->log_error_fd); + srv->log_error_fd = -1; +#ifdef HAVE_SYSLOG_H + /* fall back to syslog() */ + srv->log_using_syslog = 1; + + openlog("lighttpd", LOG_CONS, LOG_LOCAL0); +#endif + } else { + /* ok, new log is open, close the old one */ + close(srv->log_error_fd); + srv->log_error_fd = new_fd; + } + } + + log_error_write(srv, __FILE__, __LINE__, "s", "logfiles cycled"); + + return 0; +} + +int log_error_close(server *srv) { + log_error_write(srv, __FILE__, __LINE__, "s", "server stopped"); + + if (srv->log_error_fd >= 0) { + close(srv->log_error_fd); + } else if(srv->log_using_syslog) { +#ifdef HAVE_SYSLOG_H + closelog(); +#endif + } + + return 0; +} + +int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...) { + va_list ap; + + if (srv->log_using_syslog == 0) { + /* cache the generated timestamp */ + if (srv->cur_ts != srv->last_generated_debug_ts) { + buffer_prepare_copy(srv->ts_debug_str, 255); + strftime(srv->ts_debug_str->ptr, srv->ts_debug_str->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(srv->cur_ts))); + srv->ts_debug_str->used = strlen(srv->ts_debug_str->ptr) + 1; + + srv->last_generated_debug_ts = srv->cur_ts; + } + + buffer_copy_string_buffer(srv->error_log, srv->ts_debug_str); + BUFFER_APPEND_STRING_CONST(srv->error_log, ": ("); + } else { + BUFFER_COPY_STRING_CONST(srv->error_log, "("); + } + buffer_append_string(srv->error_log, filename); + BUFFER_APPEND_STRING_CONST(srv->error_log, "."); + buffer_append_long(srv->error_log, line); + BUFFER_APPEND_STRING_CONST(srv->error_log, ") "); + + + for(va_start(ap, fmt); *fmt; fmt++) { + int d; + char *s; + buffer *b; + off_t o; + + switch(*fmt) { + case 's': /* string */ + s = va_arg(ap, char *); + buffer_append_string(srv->error_log, s); + BUFFER_APPEND_STRING_CONST(srv->error_log, " "); + break; + case 'b': /* buffer */ + b = va_arg(ap, buffer *); + buffer_append_string_buffer(srv->error_log, b); + BUFFER_APPEND_STRING_CONST(srv->error_log, " "); + break; + case 'd': /* int */ + d = va_arg(ap, int); + buffer_append_long(srv->error_log, d); + BUFFER_APPEND_STRING_CONST(srv->error_log, " "); + break; + case 'o': /* off_t */ + o = va_arg(ap, off_t); + buffer_append_off_t(srv->error_log, o); + BUFFER_APPEND_STRING_CONST(srv->error_log, " "); + break; + case 'x': /* int (hex) */ + d = va_arg(ap, int); + BUFFER_APPEND_STRING_CONST(srv->error_log, "0x"); + buffer_append_hex(srv->error_log, d); + BUFFER_APPEND_STRING_CONST(srv->error_log, " "); + break; + case 'S': /* string */ + s = va_arg(ap, char *); + buffer_append_string(srv->error_log, s); + break; + case 'B': /* buffer */ + b = va_arg(ap, buffer *); + buffer_append_string_buffer(srv->error_log, b); + break; + case 'D': /* int */ + d = va_arg(ap, int); + buffer_append_long(srv->error_log, d); + break; + case '(': + case ')': + case '<': + case '>': + case ',': + case ' ': + buffer_append_string_len(srv->error_log, fmt, 1); + break; + } + } + va_end(ap); + + BUFFER_APPEND_STRING_CONST(srv->error_log, "\n"); + + if (srv->log_error_fd >= 0) { + write(srv->log_error_fd, srv->error_log->ptr, srv->error_log->used - 1); + } else if (srv->log_using_syslog == 0) { + /* only available at startup time */ + write(STDERR_FILENO, srv->error_log->ptr, srv->error_log->used - 1); + } else { +#ifdef HAVE_SYSLOG_H + syslog(LOG_ERR, "%s", srv->error_log->ptr); +#endif + } + + return 0; +} + diff --git a/src/log.h b/src/log.h new file mode 100644 index 00000000..bffee3a2 --- /dev/null +++ b/src/log.h @@ -0,0 +1,13 @@ +#ifndef _LOG_H_ +#define _LOG_H_ + +#include "server.h" + +#define WP() log_error_write(srv, __FILE__, __LINE__, ""); + +int log_error_open(server *srv); +int log_error_close(server *srv); +int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...); +int log_error_cycle(server *srv); + +#endif diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 00000000..5696b71a --- /dev/null +++ b/src/md5.c @@ -0,0 +1,356 @@ +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#include "config.h" + +#ifndef USE_OPENSSL +#include <string.h> + +#include "md5_global.h" +#include "md5.h" + +/* Constants for MD5Transform routine. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64])); +static void Encode PROTO_LIST + ((unsigned char *, UINT4 *, unsigned int)); +static void Decode PROTO_LIST + ((UINT4 *, unsigned char *, unsigned int)); + +#ifdef HAVE_MEMCPY +#define MD5_memcpy(output, input, len) memcpy((output), (input), (len)) +#else +static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int)); +#endif +#ifdef HAVE_MEMSET +#define MD5_memset(output, value, len) memset((output), (value), (len)) +#else +static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int)); +#endif + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5_Init (context) +MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5_Update (context, input, inputLen) +MD5_CTX *context; /* context */ +unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, ndx, partLen; + + /* Compute number of bytes mod 64 */ + ndx = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - ndx; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((POINTER)&context->buffer[ndx], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + ndx = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[ndx], (POINTER)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5_Final (digest, context) +unsigned char digest[16]; /* message digest */ +MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int ndx, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + ndx = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (ndx < 56) ? (56 - ndx) : (120 - ndx); + MD5_Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5_Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +UINT4 state[4]; +unsigned char block[64]; +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +UINT4 *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +UINT4 *output; +unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ +#ifndef HAVE_MEMCPY +static void MD5_memcpy (output, input, len) +POINTER output; +POINTER input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + + output[i] = input[i]; +} +#endif +/* Note: Replace "for loop" with standard memset if possible. + */ +#ifndef HAVE_MEMSET +static void MD5_memset (output, value, len) +POINTER output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} +#endif +#endif diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 00000000..4b601484 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,37 @@ +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5_Init PROTO_LIST ((MD5_CTX *)); +void MD5_Update PROTO_LIST + ((MD5_CTX *, unsigned char *, unsigned int)); +void MD5_Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); + diff --git a/src/md5_global.h b/src/md5_global.h new file mode 100644 index 00000000..6a5c94ff --- /dev/null +++ b/src/md5_global.h @@ -0,0 +1,31 @@ +/* GLOBAL.H - RSAREF types and constants + */ + +/* PROTOTYPES should be set to one if and only if the compiler supports + function argument prototyping. +The following makes PROTOTYPES default to 0 if it has not already + + been defined with C compiler flags. + */ +#ifndef PROTOTYPES +#define PROTOTYPES 0 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. +If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif diff --git a/src/mod_access.c b/src/mod_access.c new file mode 100644 index 00000000..b2c81cfb --- /dev/null +++ b/src/mod_access.c @@ -0,0 +1,177 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "config.h" + +typedef struct { + array *access_deny; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_access_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +FREE_FUNC(mod_access_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->access_deny); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_access_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "url.access-deny", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->access_deny = array_init(); + + cv[0].destination = s->access_deny; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_access_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.access-deny"))) { + PATCH(access_deny); + } + } + } + + return 0; +} + +static int mod_access_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + + UNUSED(srv); + UNUSED(con); + + PATCH(access_deny); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_access_uri_handler) { + plugin_data *p = p_d; + int s_len; + size_t k, i; + + UNUSED(srv); + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_access_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_access_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + s_len = con->uri.path->used - 1; + + for (k = 0; k < p->conf.access_deny->used; k++) { + data_string *ds = (data_string *)p->conf.access_deny->data[k]; + int ct_len = ds->value->used - 1; + + if (ct_len > s_len) continue; + + if (ds->value->used == 0) continue; + + if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) { + con->http_status = 403; + + return HANDLER_FINISHED; + } + } + + /* not found */ + return HANDLER_GO_ON; +} + + +int mod_access_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("access"); + + p->init = mod_access_init; + p->set_defaults = mod_access_set_defaults; + p->handle_uri_clean = mod_access_uri_handler; + p->cleanup = mod_access_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_accesslog.c b/src/mod_accesslog.c new file mode 100644 index 00000000..22574b12 --- /dev/null +++ b/src/mod_accesslog.c @@ -0,0 +1,840 @@ +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <stdio.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "inet_ntop_cache.h" + +#include "sys-socket.h" + +#ifdef HAVE_SYSLOG_H +# include <syslog.h> +#endif + +typedef struct { + char key; + enum { + FORMAT_UNSET, + FORMAT_UNSUPPORTED, + FORMAT_PERCENT, + FORMAT_REMOTE_HOST, + FORMAT_REMOTE_IDENT, + FORMAT_REMOTE_USER, + FORMAT_TIMESTAMP, + FORMAT_REQUEST_LINE, + FORMAT_STATUS, + FORMAT_BYTES_OUT_NO_HEADER, + FORMAT_HEADER, + + FORMAT_REMOTE_ADDR, + FORMAT_LOCAL_ADDR, + FORMAT_COOKIE, + FORMAT_TIME_USED_MS, + FORMAT_ENV, + FORMAT_FILENAME, + FORMAT_REQUEST_PROTOCOL, + FORMAT_REQUEST_METHOD, + FORMAT_SERVER_PORT, + FORMAT_QUERY_STRING, + FORMAT_TIME_USED, + FORMAT_URL, + FORMAT_SERVER_NAME, + FORMAT_CONNECTION_STATUS, + FORMAT_BYTES_IN, + FORMAT_BYTES_OUT, + + FORMAT_RESPONSE_HEADER + } type; +} format_mapping; + +/** + * + * + * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" + * + */ + +const format_mapping fmap[] = +{ + { '%', FORMAT_PERCENT }, + { 'h', FORMAT_REMOTE_HOST }, + { 'l', FORMAT_REMOTE_IDENT }, + { 'u', FORMAT_REMOTE_USER }, + { 't', FORMAT_TIMESTAMP }, + { 'r', FORMAT_REQUEST_LINE }, + { 's', FORMAT_STATUS }, + { 'b', FORMAT_BYTES_OUT_NO_HEADER }, + { 'i', FORMAT_HEADER }, + + { 'a', FORMAT_REMOTE_ADDR }, + { 'A', FORMAT_LOCAL_ADDR }, + { 'B', FORMAT_BYTES_OUT_NO_HEADER }, + { 'C', FORMAT_COOKIE }, + { 'D', FORMAT_TIME_USED_MS }, + { 'e', FORMAT_ENV }, + { 'f', FORMAT_FILENAME }, + { 'H', FORMAT_REQUEST_PROTOCOL }, + { 'm', FORMAT_REQUEST_METHOD }, + { 'n', FORMAT_UNSUPPORTED }, /* we have no notes */ + { 'p', FORMAT_SERVER_PORT }, + { 'P', FORMAT_UNSUPPORTED }, /* we are only one process */ + { 'q', FORMAT_QUERY_STRING }, + { 'T', FORMAT_TIME_USED }, + { 'U', FORMAT_URL }, /* w/o querystring */ + { 'v', FORMAT_SERVER_NAME }, + { 'V', FORMAT_UNSUPPORTED }, + { 'X', FORMAT_CONNECTION_STATUS }, + { 'I', FORMAT_BYTES_IN }, + { 'O', FORMAT_BYTES_OUT }, + + { 'o', FORMAT_RESPONSE_HEADER }, + + { '\0', FORMAT_UNSET } +}; + + +typedef struct { + enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type; + + buffer *string; + int field; +} format_field; + +typedef struct { + format_field **ptr; + + size_t used; + size_t size; +} format_fields; + +typedef struct { + buffer *access_logfile; + buffer *format; + unsigned short use_syslog; + + + int log_access_fd; + time_t last_generated_accesslog_ts; + time_t *last_generated_accesslog_ts_ptr; + + + buffer *access_logbuffer; + buffer *ts_accesslog_str; + + format_fields *parsed_format; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_accesslog_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +int accesslog_parse_format(server *srv, format_fields *fields, buffer *format) { + size_t i, j, k = 0, start = 0; + + for (i = 0; i < format->used - 1; i++) { + + switch(format->ptr[i]) { + case '%': + if (start != i) { + /* copy the string */ + if (fields->size == 0) { + fields->size = 16; + fields->used = 0; + fields->ptr = malloc(fields->size * sizeof(format_fields * )); + } else if (fields->used == fields->size) { + fields->size += 16; + fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * )); + } + + fields->ptr[fields->used] = malloc(sizeof(format_fields)); + fields->ptr[fields->used]->type = FIELD_STRING; + fields->ptr[fields->used]->string = buffer_init(); + + buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start); + + fields->used++; + } + + + /* we need a new field */ + + if (fields->size == 0) { + fields->size = 16; + fields->used = 0; + fields->ptr = malloc(fields->size * sizeof(format_fields * )); + } else if (fields->used == fields->size) { + fields->size += 16; + fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * )); + } + + /* search for the terminating command */ + switch (format->ptr[i+1]) { + case '>': + case '<': + /* only for s */ + + for (j = 0; fmap[j].key != '\0'; j++) { + if (fmap[j].key != format->ptr[i+2]) continue; + + /* found key */ + + fields->ptr[fields->used] = malloc(sizeof(format_fields)); + fields->ptr[fields->used]->type = FIELD_FORMAT; + fields->ptr[fields->used]->field = fmap[j].type; + fields->ptr[fields->used]->string = NULL; + + fields->used++; + + break; + } + + if (fmap[j].key == '\0') { + log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed"); + return -1; + } + + start = i + 3; + + break; + case '{': + /* go forward to } */ + + for (k = i+2; k < format->used - 1; k++) { + if (format->ptr[k] == '}') break; + } + + if (k == format->used - 1) { + log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed"); + return -1; + } + if (format->ptr[k+1] == '\0') { + log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed"); + return -1; + } + + for (j = 0; fmap[j].key != '\0'; j++) { + if (fmap[j].key != format->ptr[k+1]) continue; + + /* found key */ + + fields->ptr[fields->used] = malloc(sizeof(format_fields)); + fields->ptr[fields->used]->type = FIELD_FORMAT; + fields->ptr[fields->used]->field = fmap[j].type; + fields->ptr[fields->used]->string = buffer_init(); + + buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + i + 2, k - (i + 2)); + + fields->used++; + + break; + } + + if (fmap[j].key == '\0') { + log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed"); + return -1; + } + + start = k + 2; + + break; + default: + for (j = 0; fmap[j].key != '\0'; j++) { + if (fmap[j].key != format->ptr[i+1]) continue; + + /* found key */ + + fields->ptr[fields->used] = malloc(sizeof(format_fields)); + fields->ptr[fields->used]->type = FIELD_FORMAT; + fields->ptr[fields->used]->field = fmap[j].type; + fields->ptr[fields->used]->string = NULL; + + fields->used++; + + break; + } + + if (fmap[j].key == '\0') { + log_error_write(srv, __FILE__, __LINE__, "ss", "config: ", "failed"); + return -1; + } + + start = i + 2; + + break; + } + + break; + } + } + + if (start < i) { + /* copy the string */ + if (fields->size == 0) { + fields->size = 16; + fields->used = 0; + fields->ptr = malloc(fields->size * sizeof(format_fields * )); + } else if (fields->used == fields->size) { + fields->size += 16; + fields->ptr = realloc(fields->ptr, fields->size * sizeof(format_fields * )); + } + + fields->ptr[fields->used] = malloc(sizeof(format_fields)); + fields->ptr[fields->used]->type = FIELD_STRING; + fields->ptr[fields->used]->string = buffer_init(); + + buffer_copy_string_len(fields->ptr[fields->used]->string, format->ptr + start, i - start); + + fields->used++; + } + + return 0; +} + +FREE_FUNC(mod_accesslog_free) { + plugin_data *p = p_d; + size_t i; + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if (s->access_logbuffer->used) { + if (s->use_syslog) { +# ifdef HAVE_SYSLOG_H + syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 1, s->access_logbuffer->ptr); +# endif + } else if (s->log_access_fd != -1) { + write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1); + } + } + + if (s->log_access_fd != -1) close(s->log_access_fd); + + buffer_free(s->ts_accesslog_str); + buffer_free(s->access_logbuffer); + buffer_free(s->format); + buffer_free(s->access_logfile); + + if (s->parsed_format) { + size_t j; + for (j = 0; j < s->parsed_format->used; j++) { + if (s->parsed_format->ptr[j]->string) buffer_free(s->parsed_format->ptr[j]->string); + free(s->parsed_format->ptr[j]); + } + free(s->parsed_format->ptr); + free(s->parsed_format); + } + + free(s); + } + + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(log_access_open) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "accesslog.filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "accesslog.use-syslog", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, + { "accesslog.format", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->access_logfile = buffer_init(); + s->format = buffer_init(); + s->access_logbuffer = buffer_init(); + s->ts_accesslog_str = buffer_init(); + s->log_access_fd = -1; + s->last_generated_accesslog_ts = 0; + s->last_generated_accesslog_ts_ptr = &(s->last_generated_accesslog_ts); + + + cv[0].destination = s->access_logfile; + cv[1].destination = &(s->use_syslog); + cv[2].destination = s->format; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + + if (i == 0 && buffer_is_empty(s->format)) { + /* set a default logfile string */ + + buffer_copy_string(s->format, "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""); + } + + /* parse */ + + if (s->format->used) { + s->parsed_format = calloc(1, sizeof(*(s->parsed_format))); + + if (-1 == accesslog_parse_format(srv, s->parsed_format, s->format)) { + log_error_write(srv, __FILE__, __LINE__, "s", "config: ", "failed"); + return HANDLER_ERROR; + } +#if 0 + /* debugging */ + for (j = 0; j < s->parsed_format->used; j++) { + switch (s->parsed_format->ptr[j]->type) { + case FIELD_FORMAT: + log_error_write(srv, __FILE__, __LINE__, "ssds", + "config:", "format", s->parsed_format->ptr[j]->field, + s->parsed_format->ptr[j]->string ? + s->parsed_format->ptr[j]->string->ptr : "" ); + break; + case FIELD_STRING: + log_error_write(srv, __FILE__, __LINE__, "ssbs", "config:", "string '", s->parsed_format->ptr[j]->string, "'"); + break; + default: + break; + } + } +#endif + } + + if (s->use_syslog) { + if (srv->log_using_syslog == 0) { + log_error_write(srv, __FILE__, __LINE__, "ssbs", + "accesslog can only be written to syslog if errorlog is also sent to syslog. ABORTING."); + + return HANDLER_ERROR; + } + + /* ignore the next checks */ + continue; + } + + if (buffer_is_empty(s->access_logfile)) continue; + + if (s->access_logfile->ptr[0] == '|') { +#ifdef HAVE_FORK + /* create write pipe and spawn process */ + + int to_log_fds[2]; + pid_t pid; + + if (pipe(to_log_fds)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed: ", strerror(errno)); + return -1; + } + + /* fork, execve */ + switch (pid = fork()) { + case 0: + /* child */ + + close(STDIN_FILENO); + dup2(to_log_fds[0], STDIN_FILENO); + close(to_log_fds[0]); + /* not needed */ + close(to_log_fds[1]); + + /* we don't need the client socket */ + for (i = 3; i < 256; i++) { + close(i); + } + + /* exec the log-process (skip the | ) + * + */ + + execl("/bin/sh", "sh", "-c", s->access_logfile->ptr + 1, NULL); + + log_error_write(srv, __FILE__, __LINE__, "sss", + "spawning log-process failed: ", strerror(errno), + s->access_logfile->ptr + 1); + + exit(-1); + break; + case -1: + /* error */ + log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno)); + break; + default: + close(to_log_fds[0]); + + s->log_access_fd = to_log_fds[1]; + + break; + } +#else + return -1; +#endif + } else if (-1 == (s->log_access_fd = + open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) { + + log_error_write(srv, __FILE__, __LINE__, "ssb", + "opening access-log failed:", + strerror(errno), s->access_logfile); + + return HANDLER_ERROR; + } + fcntl(s->log_access_fd, F_SETFD, FD_CLOEXEC); + + } + + return HANDLER_GO_ON; +} + +SIGHUP_FUNC(log_access_cycle) { + plugin_data *p = p_d; + size_t i; + + if (!p->config_storage) return HANDLER_GO_ON; + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if(s->access_logbuffer->used) { + if (s->use_syslog) { +#ifdef HAVE_SYSLOG_H + syslog(LOG_INFO, "%*s", s->access_logbuffer->used - 1, s->access_logbuffer->ptr); +#endif + } else if (s->log_access_fd != -1) { + write(s->log_access_fd, s->access_logbuffer->ptr, s->access_logbuffer->used - 1); + } + s->access_logbuffer->used = 0; + } + + if (s->use_syslog == 0 && + s->access_logfile->ptr[0] != '|') { + + close(s->log_access_fd); + + if (-1 == (s->log_access_fd = + open(s->access_logfile->ptr, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) { + + log_error_write(srv, __FILE__, __LINE__, "ss", "cycling access-log failed:", strerror(errno)); + + return HANDLER_ERROR; + } + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_accesslog_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.filename"))) { + PATCH(access_logfile); + PATCH(log_access_fd); + PATCH(last_generated_accesslog_ts_ptr); + PATCH(access_logbuffer); + PATCH(ts_accesslog_str); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.format"))) { + PATCH(format); + PATCH(parsed_format); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("accesslog.use-syslog"))) { + PATCH(use_syslog); + } + } + } + + return 0; +} + +static int mod_accesslog_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(access_logfile); + PATCH(format); + PATCH(log_access_fd); + PATCH(last_generated_accesslog_ts_ptr); + PATCH(access_logbuffer); + PATCH(ts_accesslog_str); + PATCH(parsed_format); + PATCH(use_syslog); + + return 0; +} +#undef PATCH + +REQUESTDONE_FUNC(log_access_write) { + plugin_data *p = p_d; + size_t j, i; + + int newts = 0; + data_string *ds; + + mod_accesslog_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_accesslog_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + if (p->conf.access_logbuffer->used == 0) { + buffer_copy_string(p->conf.access_logbuffer, ""); + } + + for (j = 0; j < p->conf.parsed_format->used; j++) { + switch(p->conf.parsed_format->ptr[j]->type) { + case FIELD_STRING: + buffer_append_string_buffer(p->conf.access_logbuffer, p->conf.parsed_format->ptr[j]->string); + break; + case FIELD_FORMAT: + switch(p->conf.parsed_format->ptr[j]->field) { + case FORMAT_TIMESTAMP: + + /* cache the generated timestamp */ + if (srv->cur_ts != *(p->conf.last_generated_accesslog_ts_ptr)) { + struct tm tm; +#if defined(HAVE_STRUCT_TM_GMTOFF) + long scd, hrs, min; +#endif + + buffer_prepare_copy(p->conf.ts_accesslog_str, 255); +#if defined(HAVE_STRUCT_TM_GMTOFF) +# ifdef HAVE_LOCALTIME_R + localtime_r(&(srv->cur_ts), &tm); + strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S ", &tm); +# else + strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S ", localtime_r(&(srv->cur_ts))); +# endif + p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1; + + buffer_append_string(p->conf.ts_accesslog_str, tm.tm_gmtoff >= 0 ? "+" : "-"); + + scd = abs(tm.tm_gmtoff); + hrs = scd / 3600; + min = (scd % 3600) / 60; + + /* hours */ + if (hrs < 10) buffer_append_string(p->conf.ts_accesslog_str, "0"); + buffer_append_long(p->conf.ts_accesslog_str, hrs); + + if (min < 10) buffer_append_string(p->conf.ts_accesslog_str, "0"); + buffer_append_long(p->conf.ts_accesslog_str, min); + BUFFER_APPEND_STRING_CONST(p->conf.ts_accesslog_str, "]"); +#else +#ifdef HAVE_GMTIME_R + gmtime_r(&(srv->cur_ts), &tm); + strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S +0000]", &tm); +#else + strftime(p->conf.ts_accesslog_str->ptr, p->conf.ts_accesslog_str->size - 1, "[%d/%b/%Y:%H:%M:%S +0000]", gmtime(&(srv->cur_ts))); +#endif + p->conf.ts_accesslog_str->used = strlen(p->conf.ts_accesslog_str->ptr) + 1; +#endif + + *(p->conf.last_generated_accesslog_ts_ptr) = srv->cur_ts; + newts = 1; + } + + buffer_append_string_buffer(p->conf.access_logbuffer, p->conf.ts_accesslog_str); + + break; + case FORMAT_REMOTE_HOST: + + /* handle inet_ntop cache */ + + buffer_append_string(p->conf.access_logbuffer, inet_ntop_cache_get_ip(srv, &(con->dst_addr))); + + break; + case FORMAT_REMOTE_IDENT: + /* ident */ + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + break; + case FORMAT_REMOTE_USER: + if (con->authed_user->used > 1) { + buffer_append_string_buffer(p->conf.access_logbuffer, con->authed_user); + } else { + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + } + break; + case FORMAT_REQUEST_LINE: + if (con->request.request_line->used) { + buffer_append_string_buffer(p->conf.access_logbuffer, con->request.request_line); + } + break; + case FORMAT_STATUS: + buffer_append_long(p->conf.access_logbuffer, con->http_status); + break; + + case FORMAT_BYTES_OUT_NO_HEADER: + if (con->bytes_written > 0) { + buffer_append_off_t(p->conf.access_logbuffer, + con->bytes_written - con->bytes_header <= 0 ? 0 : con->bytes_written - con->bytes_header); + } else { + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + } + break; + case FORMAT_HEADER: + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, p->conf.parsed_format->ptr[j]->string->ptr))) { + buffer_append_string_buffer(p->conf.access_logbuffer, ds->value); + } else { + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + } + break; + case FORMAT_RESPONSE_HEADER: + if (NULL != (ds = (data_string *)array_get_element(con->response.headers, p->conf.parsed_format->ptr[j]->string->ptr))) { + buffer_append_string_buffer(p->conf.access_logbuffer, ds->value); + } else { + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + } + break; + case FORMAT_FILENAME: + if (con->physical.path->used > 1) { + buffer_append_string_buffer(p->conf.access_logbuffer, con->physical.path); + } else { + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + } + break; + case FORMAT_BYTES_OUT: + if (con->bytes_written > 0) { + buffer_append_off_t(p->conf.access_logbuffer, con->bytes_written); + } else { + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "-"); + } + break; + case FORMAT_TIME_USED: + buffer_append_long(p->conf.access_logbuffer, srv->cur_ts - con->request_start); + break; + case FORMAT_SERVER_NAME: + buffer_append_string_buffer(p->conf.access_logbuffer, con->server_name); + break; + case FORMAT_REQUEST_PROTOCOL: + buffer_append_string(p->conf.access_logbuffer, + con->request.http_version == HTTP_VERSION_1_1 ? "HTTP/1.1" : "HTTP/1.0"); + break; + case FORMAT_REQUEST_METHOD: + switch(con->request.http_method) { + case HTTP_METHOD_GET: buffer_append_string(p->conf.access_logbuffer, "GET"); break; + case HTTP_METHOD_POST: buffer_append_string(p->conf.access_logbuffer, "POST"); break; + case HTTP_METHOD_HEAD: buffer_append_string(p->conf.access_logbuffer, "HEAD"); break; + default: break; + } + break; + case FORMAT_SERVER_PORT: + buffer_append_long(p->conf.access_logbuffer, srv->srvconf.port); + break; + case FORMAT_QUERY_STRING: + buffer_append_string_buffer(p->conf.access_logbuffer, con->uri.query); + break; + case FORMAT_URL: + buffer_append_string_buffer(p->conf.access_logbuffer, con->uri.path_raw); + break; + case FORMAT_CONNECTION_STATUS: + switch(con->keep_alive) { + case 0: buffer_append_string(p->conf.access_logbuffer, "-"); break; + default: buffer_append_string(p->conf.access_logbuffer, "+"); break; + } + break; + default: + /* + { 'a', FORMAT_REMOTE_ADDR }, + { 'A', FORMAT_LOCAL_ADDR }, + { 'C', FORMAT_COOKIE }, + { 'D', FORMAT_TIME_USED_MS }, + { 'e', FORMAT_ENV }, + { 'I', FORMAT_BYTES_IN }, + { 'O', FORMAT_BYTES_OUT }, + */ + + break; + } + break; + default: + break; + } + } + + BUFFER_APPEND_STRING_CONST(p->conf.access_logbuffer, "\n"); + + if (newts || p->conf.access_logbuffer->used > BUFFER_MAX_REUSE_SIZE) { + if (p->conf.use_syslog) { +#ifdef HAVE_SYSLOG_H + syslog(LOG_INFO, "%*s", p->conf.access_logbuffer->used - 1, p->conf.access_logbuffer->ptr); +#endif + } else if (p->conf.log_access_fd != -1) { + write(p->conf.log_access_fd, p->conf.access_logbuffer->ptr, p->conf.access_logbuffer->used - 1); + } + buffer_reset(p->conf.access_logbuffer); + } + + return HANDLER_GO_ON; +} + + +int mod_accesslog_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("accesslog"); + + p->init = mod_accesslog_init; + p->set_defaults= log_access_open; + p->cleanup = mod_accesslog_free; + + p->handle_request_done = log_access_write; + p->handle_sighup = log_access_cycle; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_alias.c b/src/mod_alias.c new file mode 100644 index 00000000..501f5fac --- /dev/null +++ b/src/mod_alias.c @@ -0,0 +1,185 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "config.h" + + +/* plugin config for all request/connections */ +typedef struct { + array *alias; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_alias_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_alias_free) { + plugin_data *p = p_d; + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->alias); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_alias_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "alias.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->alias = array_init(); + cv[0].destination = s->alias; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_alias_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("alias.url"))) { + PATCH(alias); + } + } + } + + return 0; +} + +static int mod_alias_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(alias); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_alias_docroot_handler) { + plugin_data *p = p_d; + int uri_len; + size_t k, i; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_alias_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_alias_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + uri_len = con->uri.path->used - 1; + + for (k = 0; k < p->conf.alias->used; k++) { + data_string *ds = (data_string *)p->conf.alias->data[k]; + int alias_len = ds->key->used - 1; + + if (alias_len > uri_len) continue; + if (ds->key->used == 0) continue; + + if (0 == strncmp(con->uri.path->ptr, ds->key->ptr, alias_len)) { + /* matched */ + + buffer_copy_string_buffer(con->physical.doc_root, ds->value); + buffer_copy_string(con->physical.rel_path, con->uri.path->ptr + alias_len); + + return HANDLER_GO_ON; + } + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_alias_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("alias"); + + p->init = mod_alias_init; + p->handle_docroot = mod_alias_docroot_handler; + p->set_defaults = mod_alias_set_defaults; + p->cleanup = mod_alias_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_auth.c b/src/mod_auth.c new file mode 100644 index 00000000..824b8f73 --- /dev/null +++ b/src/mod_auth.c @@ -0,0 +1,565 @@ +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "plugin.h" +#include "http_auth.h" +#include "log.h" +#include "response.h" + + +/** + * the basic and digest auth framework + * + * - config handling + * - protocol handling + * + * http_auth.c + * http_auth_digest.c + * + * do the real work + */ + +INIT_FUNC(mod_auth_init) { + mod_auth_plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + p->auth_user = buffer_init(); +#ifdef USE_LDAP + p->ldap_filter = buffer_init(); +#endif + + return p; +} + +FREE_FUNC(mod_auth_free) { + mod_auth_plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + buffer_free(p->tmp_buf); + buffer_free(p->auth_user); +#ifdef USE_LDAP + buffer_free(p->ldap_filter); +#endif + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + mod_auth_plugin_config *s = p->config_storage[i]; + + array_free(s->auth_require); + buffer_free(s->auth_plain_groupfile); + buffer_free(s->auth_plain_userfile); + buffer_free(s->auth_htdigest_userfile); + buffer_free(s->auth_htpasswd_userfile); + buffer_free(s->auth_backend_conf); + + buffer_free(s->auth_ldap_hostname); + buffer_free(s->auth_ldap_basedn); + buffer_free(s->auth_ldap_filter); + +#ifdef USE_LDAP + buffer_free(s->ldap_filter_pre); + buffer_free(s->ldap_filter_post); + + if (s->ldap) ldap_unbind_s(s->ldap); +#endif + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_auth_patch_connection(server *srv, connection *con, mod_auth_plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + mod_auth_plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend"))) { + PATCH(auth_backend); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.plain.groupfile"))) { + PATCH(auth_plain_groupfile); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.plain.userfile"))) { + PATCH(auth_plain_userfile); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.htdigest.userfile"))) { + PATCH(auth_htdigest_userfile); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.htpasswd.userfile"))) { + PATCH(auth_htpasswd_userfile); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.require"))) { + PATCH(auth_require); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.debug"))) { + PATCH(auth_debug); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.hostname"))) { + PATCH(auth_ldap_hostname); +#ifdef USE_LDAP + PATCH(ldap); + PATCH(ldap_filter_pre); + PATCH(ldap_filter_post); +#endif + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.base-dn"))) { + PATCH(auth_ldap_basedn); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("auth.backend.ldap.filter"))) { + PATCH(auth_ldap_filter); + } + } + } + + return 0; +} + +static int mod_auth_setup_connection(server *srv, connection *con, mod_auth_plugin_data *p) { + mod_auth_plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(auth_backend); + PATCH(auth_plain_groupfile); + PATCH(auth_plain_userfile); + PATCH(auth_htdigest_userfile); + PATCH(auth_htpasswd_userfile); + PATCH(auth_require); + PATCH(auth_debug); + PATCH(auth_ldap_hostname); + PATCH(auth_ldap_basedn); + PATCH(auth_ldap_filter); +#ifdef USE_LDAP + PATCH(ldap); + PATCH(ldap_filter_pre); + PATCH(ldap_filter_post); +#endif + + return 0; +} +#undef PATCH + +static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) { + size_t k; + int auth_required = 0, auth_satisfied = 0; + char *http_authorization = NULL; + data_string *ds; + mod_auth_plugin_data *p = p_d; + array *req; + size_t i; + + /* select the right config */ + mod_auth_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_auth_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + if (p->conf.auth_require == NULL) return HANDLER_GO_ON; + + /* + * AUTH + * + */ + + /* do we have to ask for auth ? */ + + auth_required = 0; + auth_satisfied = 0; + + /* search auth-directives for path */ + for (k = 0; k < p->conf.auth_require->used; k++) { + if (p->conf.auth_require->data[k]->key->used == 0) continue; + + if (0 == strncmp(con->uri.path->ptr, p->conf.auth_require->data[k]->key->ptr, p->conf.auth_require->data[k]->key->used - 1)) { + auth_required = 1; + break; + } + } + + /* nothing to do for us */ + if (auth_required == 0) return HANDLER_GO_ON; + + req = ((data_array *)(p->conf.auth_require->data[k]))->value; + + /* try to get Authorization-header */ + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Authorization"))) { + http_authorization = ds->value->ptr; + } + + if (ds && ds->value && ds->value->used) { + char *auth_realm; + data_string *method; + + method = (data_string *)array_get_element(req, "method"); + + /* parse auth-header */ + if (NULL != (auth_realm = strchr(http_authorization, ' '))) { + int auth_type_len = auth_realm - http_authorization; + + if ((auth_type_len == 5) && + (0 == strncmp(http_authorization, "Basic", auth_type_len))) { + + if (0 == strcmp(method->value->ptr, "basic")) { + auth_satisfied = http_auth_basic_check(srv, con, p, req, con->uri.path, auth_realm+1); + } + } else if ((auth_type_len == 6) && + (0 == strncmp(http_authorization, "Digest", auth_type_len))) { + if (0 == strcmp(method->value->ptr, "digest")) { + if (-1 == (auth_satisfied = http_auth_digest_check(srv, con, p, req, con->uri.path, auth_realm+1))) { + con->http_status = 400; + + /* a field was missing */ + + return HANDLER_FINISHED; + } + } + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", + "unknown authentification type:", + http_authorization); + } + } + } + + if (!auth_satisfied) { + data_string *method, *realm; + method = (data_string *)array_get_element(req, "method"); + realm = (data_string *)array_get_element(req, "realm"); + + con->http_status = 401; + + if (0 == strcmp(method->value->ptr, "basic")) { + buffer_copy_string(p->tmp_buf, "Basic realm=\""); + buffer_append_string_buffer(p->tmp_buf, realm->value); + buffer_append_string(p->tmp_buf, "\""); + + response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf)); + } else if (0 == strcmp(method->value->ptr, "digest")) { + char hh[33]; + http_auth_digest_generate_nonce(srv, p, srv->tmp_buf, hh); + + buffer_copy_string(p->tmp_buf, "Digest realm=\""); + buffer_append_string_buffer(p->tmp_buf, realm->value); + buffer_append_string(p->tmp_buf, "\", nonce=\""); + buffer_append_string(p->tmp_buf, hh); + buffer_append_string(p->tmp_buf, "\", qop=\"auth\""); + + response_header_insert(srv, con, CONST_STR_LEN("WWW-Authenticate"), CONST_BUF_LEN(p->tmp_buf)); + } else { + /* evil */ + } + return HANDLER_FINISHED; + } else { + /* the REMOTE_USER header */ + + buffer_copy_string_buffer(con->authed_user, p->auth_user); + } + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_auth_set_defaults) { + mod_auth_plugin_data *p = p_d; + size_t i; + + config_values_t cv[] = { + { "auth.backend", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "auth.backend.plain.groupfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.plain.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.require", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.ldap.hostname", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.ldap.base-dn", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.ldap.filter", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.htdigest.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.backend.htpasswd.userfile", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "auth.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + mod_auth_plugin_config *s; + size_t n; + data_array *da; + array *ca; + + s = malloc(sizeof(mod_auth_plugin_config)); + s->auth_plain_groupfile = buffer_init(); + s->auth_plain_userfile = buffer_init(); + s->auth_htdigest_userfile = buffer_init(); + s->auth_htpasswd_userfile = buffer_init(); + s->auth_backend_conf = buffer_init(); + + s->auth_ldap_hostname = buffer_init(); + s->auth_ldap_basedn = buffer_init(); + s->auth_ldap_filter = buffer_init(); + s->auth_debug = 0; + + s->auth_require = array_init(); + +#ifdef USE_LDAP + s->ldap_filter_pre = buffer_init(); + s->ldap_filter_post = buffer_init(); + s->ldap = NULL; +#endif + + cv[0].destination = s->auth_backend_conf; + cv[1].destination = s->auth_plain_groupfile; + cv[2].destination = s->auth_plain_userfile; + cv[3].destination = s->auth_require; + cv[4].destination = s->auth_ldap_hostname; + cv[5].destination = s->auth_ldap_basedn; + cv[6].destination = s->auth_ldap_filter; + cv[7].destination = s->auth_htdigest_userfile; + cv[8].destination = s->auth_htpasswd_userfile; + cv[9].destination = &(s->auth_debug); + + p->config_storage[i] = s; + ca = ((data_config *)srv->config_context->data[i])->value; + + if (0 != config_insert_values_global(srv, ca, cv)) { + return HANDLER_ERROR; + } + + if (s->auth_backend_conf->used) { + if (0 == strcmp(s->auth_backend_conf->ptr, "htpasswd")) { + s->auth_backend = AUTH_BACKEND_HTPASSWD; + } else if (0 == strcmp(s->auth_backend_conf->ptr, "htdigest")) { + s->auth_backend = AUTH_BACKEND_HTDIGEST; + } else if (0 == strcmp(s->auth_backend_conf->ptr, "plain")) { + s->auth_backend = AUTH_BACKEND_PLAIN; + } else if (0 == strcmp(s->auth_backend_conf->ptr, "ldap")) { + s->auth_backend = AUTH_BACKEND_LDAP; + } else { + log_error_write(srv, __FILE__, __LINE__, "sb", "auth.backend not supported:", s->auth_backend_conf); + + return HANDLER_ERROR; + } + } + + /* no auth.require for this section */ + if (NULL == (da = (data_array *)array_get_element(ca, "auth.require"))) continue; + + if (da->type != TYPE_ARRAY) continue; + + for (n = 0; n < da->value->used; n++) { + size_t m; + data_array *da_file = (data_array *)da->value->data[n]; + const char *method, *realm, *require; + + if (da->value->data[n]->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sssbs", + "unexpected type for key: ", "auth.require", "[", da->value->data[n]->key, "](string)"); + + return HANDLER_ERROR; + } + + method = realm = require = NULL; + + for (m = 0; m < da_file->value->used; m++) { + if (da_file->value->data[m]->type == TYPE_STRING) { + if (0 == strcmp(da_file->value->data[m]->key->ptr, "method")) { + method = ((data_string *)(da_file->value->data[m]))->value->ptr; + } else if (0 == strcmp(da_file->value->data[m]->key->ptr, "realm")) { + realm = ((data_string *)(da_file->value->data[m]))->value->ptr; + } else if (0 == strcmp(da_file->value->data[m]->key->ptr, "require")) { + require = ((data_string *)(da_file->value->data[m]))->value->ptr; + } else { + log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "auth.require", "[", da_file->value->data[m]->key, "](string)"); + return HANDLER_ERROR; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "auth.require", "[", da_file->value->data[m]->key, "](string)"); + + return HANDLER_ERROR; + } + } + + if (method == NULL) { + log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "method", "](string)"); + return HANDLER_ERROR; + } + + if (realm == NULL) { + log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "realm", "](string)"); + return HANDLER_ERROR; + } + + if (require == NULL) { + log_error_write(srv, __FILE__, __LINE__, "sssss", "missing entry for key: ", "auth.require", "[", "require", "](string)"); + return HANDLER_ERROR; + } + + if (method && realm && require) { + data_string *ds; + data_array *a; + + a = data_array_init(); + buffer_copy_string_buffer(a->key, da_file->key); + + ds = data_string_init(); + + buffer_copy_string(ds->key, "method"); + buffer_copy_string(ds->value, method); + + array_insert_unique(a->value, (data_unset *)ds); + + ds = data_string_init(); + + buffer_copy_string(ds->key, "realm"); + buffer_copy_string(ds->value, realm); + + array_insert_unique(a->value, (data_unset *)ds); + + ds = data_string_init(); + + buffer_copy_string(ds->key, "require"); + buffer_copy_string(ds->value, require); + + array_insert_unique(a->value, (data_unset *)ds); + + array_insert_unique(s->auth_require, (data_unset *)a); + } + } + + switch(s->auth_backend) { + case AUTH_BACKEND_PLAIN: + if (s->auth_plain_userfile->used) { + int fd; + /* try to read */ + if (-1 == (fd = open(s->auth_plain_userfile->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening auth.backend.plain.userfile:", s->auth_plain_userfile, + "failed:", strerror(errno)); + return HANDLER_ERROR; + } + close(fd); + } + break; + case AUTH_BACKEND_HTPASSWD: + if (s->auth_htpasswd_userfile->used) { + int fd; + /* try to read */ + if (-1 == (fd = open(s->auth_htpasswd_userfile->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening auth.backend.htpasswd.userfile:", s->auth_htpasswd_userfile, + "failed:", strerror(errno)); + return HANDLER_ERROR; + } + close(fd); + } + break; + case AUTH_BACKEND_HTDIGEST: + if (s->auth_htdigest_userfile->used) { + int fd; + /* try to read */ + if (-1 == (fd = open(s->auth_htdigest_userfile->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening auth.backend.htdigest.userfile:", s->auth_htdigest_userfile, + "failed:", strerror(errno)); + return HANDLER_ERROR; + } + close(fd); + } + break; + case AUTH_BACKEND_LDAP: +#ifdef USE_LDAP +#if 0 + if (s->auth_ldap_basedn->used == 0) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.base-dn has to be set"); + + return HANDLER_ERROR; + } +#endif + + if (s->auth_ldap_filter->used) { + char *dollar; + + /* parse filter */ + + if (NULL == (dollar = strchr(s->auth_ldap_filter->ptr, '$'))) { + log_error_write(srv, __FILE__, __LINE__, "s", "ldap: auth.backend.ldap.filter is missing a replace-operator '$'"); + + return HANDLER_ERROR; + } + + buffer_copy_string_len(s->ldap_filter_pre, s->auth_ldap_filter->ptr, dollar - s->auth_ldap_filter->ptr); + buffer_copy_string(s->ldap_filter_post, dollar+1); + } + + if (s->auth_ldap_hostname->used) { + int ret; + if (NULL == (s->ldap = ldap_init(s->auth_ldap_hostname->ptr, LDAP_PORT))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno)); + + return HANDLER_ERROR; + } + + ret = LDAP_VERSION3; + if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(s->ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } + + + /* 1. */ + if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(s->ldap, NULL, NULL))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret)); + + return HANDLER_ERROR; + } + } +#else + log_error_write(srv, __FILE__, __LINE__, "s", "no ldap support available"); + return HANDLER_ERROR; +#endif + break; + default: + break; + } + } + + return HANDLER_GO_ON; +} + +int mod_auth_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("auth"); + p->init = mod_auth_init; + p->set_defaults = mod_auth_set_defaults; + p->handle_uri_clean = mod_auth_uri_handler; + p->cleanup = mod_auth_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_auth.h b/src/mod_auth.h new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/mod_auth.h diff --git a/src/mod_cgi.c b/src/mod_cgi.c new file mode 100644 index 00000000..ff91d5f7 --- /dev/null +++ b/src/mod_cgi.c @@ -0,0 +1,1191 @@ +#include <sys/types.h> +#ifdef __WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> +#endif + +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <fdevent.h> +#include <signal.h> +#include <ctype.h> +#include <assert.h> + +#include <stdio.h> + +#include "server.h" +#include "keyvalue.h" +#include "log.h" +#include "connections.h" +#include "joblist.h" +#include "http_chunk.h" + +#include "plugin.h" + +enum {EOL_UNSET, EOL_N, EOL_RN}; + +typedef struct { + char **ptr; + + size_t size; + size_t used; +} char_array; + +typedef struct { + pid_t *ptr; + size_t used; + size_t size; +} buffer_pid_t; + +typedef struct { + array *cgi; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + buffer_pid_t cgi_pid; + + buffer *tmp_buf; + buffer *parse_response; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +typedef struct { + pid_t pid; + int fd; + int fde_ndx; /* index into the fd-event buffer */ + + connection *remote_conn; /* dumb pointer */ + plugin_data *plugin_data; /* dumb pointer */ + + buffer *response; + buffer *response_header; +} handler_ctx; + +static handler_ctx * cgi_handler_ctx_init() { + handler_ctx *hctx = calloc(1, sizeof(*hctx)); + + assert(hctx); + + hctx->response = buffer_init(); + hctx->response_header = buffer_init(); + + return hctx; +} + +static void cgi_handler_ctx_free(handler_ctx *hctx) { + buffer_free(hctx->response); + buffer_free(hctx->response_header); + + free(hctx); +} + +enum {FDEVENT_HANDLED_UNSET, FDEVENT_HANDLED_FINISHED, FDEVENT_HANDLED_NOT_FINISHED, FDEVENT_HANDLED_ERROR}; + +INIT_FUNC(mod_cgi_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + assert(p); + + p->tmp_buf = buffer_init(); + p->parse_response = buffer_init(); + + return p; +} + + +FREE_FUNC(mod_cgi_free) { + plugin_data *p = p_d; + buffer_pid_t *r = &(p->cgi_pid); + + UNUSED(srv); + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->cgi); + + free(s); + } + free(p->config_storage); + } + + + if (r->ptr) free(r->ptr); + + buffer_free(p->tmp_buf); + buffer_free(p->parse_response); + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET} + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + assert(s); + + s->cgi = array_init(); + + cv[0].destination = s->cgi; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + + +static int cgi_pid_add(server *srv, plugin_data *p, pid_t pid) { + int m = -1; + size_t i; + buffer_pid_t *r = &(p->cgi_pid); + + UNUSED(srv); + + for (i = 0; i < r->used; i++) { + if (r->ptr[i] > m) m = r->ptr[i]; + } + + if (r->size == 0) { + r->size = 16; + r->ptr = malloc(sizeof(*r->ptr) * r->size); + } else if (r->used == r->size) { + r->size += 16; + r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size); + } + + r->ptr[r->used++] = pid; + + return m; +} + +static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) { + size_t i; + buffer_pid_t *r = &(p->cgi_pid); + + UNUSED(srv); + + for (i = 0; i < r->used; i++) { + if (r->ptr[i] == pid) break; + } + + if (i != r->used) { + /* found */ + + if (i != r->used - 1) { + r->ptr[i] = r->ptr[r->used - 1]; + } + r->used--; + } + + return 0; +} + +static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in, int eol) { + char *ns; + const char *s; + int line = 0; + + UNUSED(srv); + + buffer_copy_string_buffer(p->parse_response, in); + + for (s = p->parse_response->ptr; + NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n'))); + s = ns + (eol == EOL_RN ? 2 : 1), line++) { + const char *key, *value; + int key_len; + data_string *ds; + + ns[0] = '\0'; + + if (line == 0 && + 0 == strncmp(s, "HTTP/1.", 7)) { + /* non-parsed header ... we parse them anyway */ + + if ((s[7] == '1' || + s[7] == '0') && + s[8] == ' ') { + int status; + /* after the space should be a status code for us */ + + status = strtol(s+9, NULL, 10); + + if (con->http_status >= 100 && + con->http_status < 1000) { + /* we expected 3 digits and didn't got them */ + con->parsed_response |= HTTP_STATUS; + con->http_status = status; + } + } + } else { + + key = s; + if (NULL == (value = strchr(s, ':'))) { + /* we expect: "<key>: <value>\r\n" */ + continue; + } + + key_len = value - key; + value += 1; + + /* skip LWS */ + while (*value == ' ' || *value == '\t') value++; + + if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { + ds = data_response_init(); + } + buffer_copy_string_len(ds->key, key, key_len); + buffer_copy_string(ds->value, value); + + array_insert_unique(con->response.headers, (data_unset *)ds); + + switch(key_len) { + case 4: + if (0 == strncasecmp(key, "Date", key_len)) { + con->parsed_response |= HTTP_DATE; + } + break; + case 6: + if (0 == strncasecmp(key, "Status", key_len)) { + con->http_status = strtol(value, NULL, 10); + con->parsed_response |= HTTP_STATUS; + } + break; + case 8: + if (0 == strncasecmp(key, "Location", key_len)) { + con->parsed_response |= HTTP_LOCATION; + } + break; + case 10: + if (0 == strncasecmp(key, "Connection", key_len)) { + con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0; + con->parsed_response |= HTTP_CONNECTION; + } + break; + case 14: + if (0 == strncasecmp(key, "Content-Length", key_len)) { + con->response.content_length = strtol(value, NULL, 10); + con->parsed_response |= HTTP_CONTENT_LENGTH; + } + break; + default: + break; + } + } + } + + /* CGI/1.1 rev 03 - 7.2.1.2 */ + if ((con->parsed_response & HTTP_LOCATION) && + !(con->parsed_response & HTTP_STATUS)) { + con->http_status = 302; + } + + return 0; +} + + +static int cgi_demux_response(server *srv, handler_ctx *hctx) { + plugin_data *p = hctx->plugin_data; + connection *con = hctx->remote_conn; + + while(1) { + int n; + + buffer_prepare_copy(hctx->response, 1024); + if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { + if (errno == EAGAIN || errno == EINTR) { + /* would block, wait for signal */ + return FDEVENT_HANDLED_NOT_FINISHED; + } + /* error */ + log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd); + return FDEVENT_HANDLED_ERROR; + } + + if (n == 0) { + /* read finished */ + + con->file_finished = 1; + + /* send final chunk */ + http_chunk_append_mem(srv, con, NULL, 0); + joblist_append(srv, con); + + return FDEVENT_HANDLED_FINISHED; + } + + hctx->response->ptr[n] = '\0'; + hctx->response->used = n+1; + + /* split header from body */ + + if (con->file_started == 0) { + char *c; + int in_header = 0; + int header_end = 0; + int cp, eol = EOL_UNSET; + size_t used = 0; + + buffer_append_string_buffer(hctx->response_header, hctx->response); + + /* nph (non-parsed headers) */ + if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1; + + /* search for the \r\n\r\n or \n\n in the string */ + for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) { + if (*c == ':') in_header = 1; + else if (*c == '\n') { + if (in_header == 0) { + /* got a response without a response header */ + + c = NULL; + header_end = 1; + break; + } + + if (eol == EOL_UNSET) eol = EOL_N; + + if (*(c+1) == '\n') { + header_end = 1; + break; + } + + } else if (used > 1 && *c == '\r' && *(c+1) == '\n') { + if (in_header == 0) { + /* got a response without a response header */ + + c = NULL; + header_end = 1; + break; + } + + if (eol == EOL_UNSET) eol = EOL_RN; + + if (used > 3 && + *(c+2) == '\r' && + *(c+3) == '\n') { + header_end = 1; + break; + } + + /* skip the \n */ + c++; + cp++; + used--; + } + } + + if (header_end) { + if (c == NULL) { + /* no header, but a body */ + + if (con->request.http_version == HTTP_VERSION_1_1) { + con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + } + + http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); + joblist_append(srv, con); + } else { + size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2); + size_t blen = hctx->response_header->used - hlen - 1; + + /* a small hack: terminate after at the second \r */ + hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1); + hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0'; + + /* parse the response header */ + cgi_response_parse(srv, con, p, hctx->response_header, eol); + + /* enable chunked-transfer-encoding */ + if (con->request.http_version == HTTP_VERSION_1_1 && + !(con->parsed_response & HTTP_CONTENT_LENGTH)) { + con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + } + + if ((hctx->response->used != hlen) && blen > 0) { + http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1); + joblist_append(srv, con); + } + } + + con->file_started = 1; + } + } else { + http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); + joblist_append(srv, con); + } + +#if 0 + log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr); +#endif + } + + return FDEVENT_HANDLED_NOT_FINISHED; +} + +static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) { + int status; + pid_t pid; + plugin_data *p; + connection *con; + + if (NULL == hctx) return HANDLER_GO_ON; + + p = hctx->plugin_data; + con = hctx->remote_conn; + + if (con->mode != p->id) return HANDLER_GO_ON; + +#ifndef __WIN32 + +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdd", + "emergency exit: cgi", + con->fd, + hctx->fd); +#endif + + /* the connection to the browser went away, but we still have a connection + * to the CGI script + * + * close cgi-connection + */ + + /* close connection to the cgi-script */ + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + + if (close(hctx->fd)) { + log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno)); + } + + hctx->fd = -1; + hctx->fde_ndx = -1; + + pid = hctx->pid; + + con->plugin_ctx[p->id] = NULL; + + /* is this a good idea ? */ + cgi_handler_ctx_free(hctx); + + /* if waitpid hasn't been called by response.c yet, do it here */ + if (pid) { + /* check if the CGI-script is already gone */ + switch(waitpid(pid, &status, WNOHANG)) { + case 0: + /* not finished yet */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", pid); +#endif + break; + case -1: + /* */ + if (errno == EINTR) break; + + /* + * errno == ECHILD happens if _subrequest catches the process-status before + * we have read the response of the cgi process + * + * -> catch status + * -> WAIT_FOR_EVENT + * -> read response + * -> we get here with waitpid == ECHILD + * + */ + if (errno == ECHILD) return HANDLER_FINISHED; + + log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno)); + return HANDLER_ERROR; + default: + if (WIFEXITED(status)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", pid); +#endif + pid = 0; + + return HANDLER_FINISHED; + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid); + pid = 0; + return HANDLER_FINISHED; + } + } + + + kill(pid, SIGTERM); + + /* cgi-script is still alive, queue the PID for removal */ + cgi_pid_add(srv, p, pid); + } +#endif + return HANDLER_FINISHED; +} + +static handler_t cgi_connection_close_callback(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + return cgi_connection_close(srv, con->plugin_ctx[p->id]); +} + + +static handler_t cgi_handle_fdevent(void *s, void *ctx, int revents) { + server *srv = (server *)s; + handler_ctx *hctx = ctx; + connection *con = hctx->remote_conn; + + joblist_append(srv, con); + + if (hctx->fd == -1) { + log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "invalid cgi-fd"); + + return HANDLER_ERROR; + } + + if (revents & FDEVENT_IN) { + switch (cgi_demux_response(srv, hctx)) { + case FDEVENT_HANDLED_NOT_FINISHED: + break; + case FDEVENT_HANDLED_FINISHED: + /* we are done */ + +#if 0 + log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "finished"); +#endif + + break; + case FDEVENT_HANDLED_ERROR: + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + con->http_status = 500; + con->mode = DIRECT; + + log_error_write(srv, __FILE__, __LINE__, "s", "demuxer failed: "); + break; + } + } + + if (revents & FDEVENT_OUT) { + /* nothing to do */ + } + + /* perhaps this issue is already handled */ + if (revents & FDEVENT_HUP) { + /* check if we still have a unfinished header package which is a body in reality */ + if (con->file_started == 0 && + hctx->response_header->used) { + con->file_started = 1; + http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); + joblist_append(srv, con); + } + + if (con->file_finished == 0) { + http_chunk_append_mem(srv, con, NULL, 0); + joblist_append(srv, con); + } + + con->file_finished = 1; + + if (chunkqueue_is_empty(con->write_queue)) { + /* there is nothing left to write */ + connection_set_state(srv, con, CON_STATE_RESPONSE_END); + } else { + /* used the write-handler to finish the request on demand */ + + } + +# if 0 + log_error_write(srv, __FILE__, __LINE__, "sddd", "got HUP from cgi", con->fd, hctx->fd, revents); +# endif + + /* rtsigs didn't liked the close */ + cgi_connection_close(srv, hctx); + } else if (revents & FDEVENT_ERR) { + con->file_finished = 1; + + /* kill all connections to the cgi process */ + cgi_connection_close(srv, hctx); +#if 1 + log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR"); +#endif + return HANDLER_ERROR; + } + + return HANDLER_FINISHED; +} + + +static int cgi_env_add(char_array *env, const char *key, size_t key_len, const char *val) { + int val_len; + char *dst; + + if (!key || !val) return -1; + + val_len = strlen(val); + + dst = malloc(key_len + val_len + 3); + memcpy(dst, key, key_len); + dst[key_len] = '='; + /* add the \0 from the value */ + memcpy(dst + key_len + 1, val, val_len + 1); + + if (env->size == 0) { + env->size = 16; + env->ptr = malloc(env->size * sizeof(*env->ptr)); + } else if (env->size == env->used) { + env->size += 16; + env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr)); + } + + env->ptr[env->used++] = dst; + + return 0; +} + +static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) { + pid_t pid; + +#ifdef HAVE_IPV6 + char b2[INET6_ADDRSTRLEN + 1]; +#endif + + int to_cgi_fds[2]; + int from_cgi_fds[2]; + struct stat st; + +#ifndef __WIN32 + + /* stat the exec file */ + if (-1 == (stat(cgi_handler->ptr, &st))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "stat for cgi-handler", cgi_handler, + "failed:", strerror(errno)); + return -1; + } + + if (pipe(to_cgi_fds)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); + return -1; + } + + if (pipe(from_cgi_fds)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); + return -1; + } + + /* fork, execve */ + switch (pid = fork()) { + case 0: { + /* child */ + char **args; + int argc; + int i = 0; + char buf[32]; + size_t n; + char_array env; + char *c; + server_socket *srv_sock = con->srv_socket; + + /* move stdout to from_cgi_fd[1] */ + close(STDOUT_FILENO); + dup2(from_cgi_fds[1], STDOUT_FILENO); + close(from_cgi_fds[1]); + /* not needed */ + close(from_cgi_fds[0]); + + /* move the stdin to to_cgi_fd[0] */ + close(STDIN_FILENO); + dup2(to_cgi_fds[0], STDIN_FILENO); + close(to_cgi_fds[0]); + /* not needed */ + close(to_cgi_fds[1]); + + /* create environment */ + env.ptr = NULL; + env.size = 0; + env.used = 0; + +#ifdef PACKAGE_NAME + cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION); +#else + cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), PACKAGE"/"VERSION); +#endif + cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), + con->server_name->used ? + con->server_name->ptr : +#ifdef HAVE_IPV6 + inet_ntop(srv_sock->addr.plain.sa_family, + srv_sock->addr.plain.sa_family == AF_INET6 ? + (const void *) &(srv_sock->addr.ipv6.sin6_addr) : + (const void *) &(srv_sock->addr.ipv4.sin_addr), + b2, sizeof(b2)-1) +#else + inet_ntoa(srv_sock->addr.ipv4.sin_addr) +#endif + ); + cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), "CGI/1.1"); + + cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version)); + + ltostr(buf, +#ifdef HAVE_IPV6 + ntohs(srv_sock->addr.plain.sa_family == AF_INET6 ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port) +#else + ntohs(srv_sock->addr.ipv4.sin_port) +#endif + ); + cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf); + cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), get_http_method_name(con->request.http_method)); + if (con->request.pathinfo->used) { + cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), con->request.pathinfo->ptr); + } + cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), "200"); + cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : ""); + + + cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), +#ifdef HAVE_IPV6 + inet_ntop(con->dst_addr.plain.sa_family, + con->dst_addr.plain.sa_family == AF_INET6 ? + (const void *) &(con->dst_addr.ipv6.sin6_addr) : + (const void *) &(con->dst_addr.ipv4.sin_addr), + b2, sizeof(b2)-1) +#else + inet_ntoa(con->dst_addr.ipv4.sin_addr) +#endif + ); + + if (con->authed_user->used) { + cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"), + con->authed_user->ptr); + } + + /* request.content_length < SSIZE_MAX, see request.c */ + ltostr(buf, con->request.content_length); + cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf); + cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), con->physical.path->ptr); + cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), con->uri.path->ptr); + + /* for valgrind */ + cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), getenv("LD_PRELOAD")); + cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), getenv("LD_LIBRARY_PATH")); +#ifdef __CYGWIN__ + /* CYGWIN needs SYSTEMROOT */ + cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), getenv("SYSTEMROOT")); +#endif + + for (n = 0; n < con->request.headers->used; n++) { + data_string *ds; + + ds = (data_string *)con->request.headers->data[n]; + + if (ds->value->used && ds->key->used) { + size_t j; + + buffer_reset(p->tmp_buf); + + if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { + buffer_copy_string(p->tmp_buf, "HTTP_"); + p->tmp_buf->used--; /* strip \0 after HTTP_ */ + } + + buffer_prepare_append(p->tmp_buf, ds->key->used + 2); + + for (j = 0; j < ds->key->used - 1; j++) { + p->tmp_buf->ptr[p->tmp_buf->used++] = + isalpha((unsigned char)ds->key->ptr[j]) ? + toupper((unsigned char)ds->key->ptr[j]) : '_'; + } + p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; + + cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), ds->value->ptr); + } + } + + for (n = 0; n < con->environment->used; n++) { + data_string *ds; + + ds = (data_string *)con->environment->data[n]; + + if (ds->value->used && ds->key->used) { + size_t j; + + buffer_reset(p->tmp_buf); + + buffer_prepare_append(p->tmp_buf, ds->key->used + 2); + + for (j = 0; j < ds->key->used - 1; j++) { + p->tmp_buf->ptr[p->tmp_buf->used++] = + isalpha((unsigned char)ds->key->ptr[j]) ? + toupper((unsigned char)ds->key->ptr[j]) : '_'; + } + p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; + + cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), ds->value->ptr); + } + } + + if (env.size == env.used) { + env.size += 16; + env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr)); + } + + env.ptr[env.used] = NULL; + + /* set up args */ + argc = 3; + args = malloc(sizeof(*args) * argc); + i = 0; + + if (cgi_handler->used > 1) { + args[i++] = cgi_handler->ptr; + } + args[i++] = con->physical.path->ptr; + args[i++] = NULL; + + /* search for the last / */ + if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) { + *c = '\0'; + + /* change to the physical directory */ + if (-1 == chdir(con->physical.path->ptr)) { + log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path); + } + *c = '/'; + } + + /* we don't need the client socket */ + for (i = 3; i < 256; i++) { + if (i != srv->log_error_fd) close(i); + } + + /* exec the cgi */ + execve(args[0], args, env.ptr); + + log_error_write(srv, __FILE__, __LINE__, "sss", "CGI failed:", strerror(errno), args[0]); + + /* */ + SEGFAULT(); + break; + } + case -1: + /* error */ + log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); + break; + default: { + handler_ctx *hctx; + /* father */ + + if (con->request.content->used) { + write(to_cgi_fds[1], con->request.content->ptr, con->request.content_length); + } + + close(from_cgi_fds[1]); + + close(to_cgi_fds[0]); + close(to_cgi_fds[1]); + + /* register PID and wait for them asyncronously */ + con->mode = p->id; + buffer_reset(con->physical.path); + + hctx = cgi_handler_ctx_init(); + + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->pid = pid; + hctx->fd = from_cgi_fds[0]; + hctx->fde_ndx = -1; + + con->plugin_ctx[p->id] = hctx; + + fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + + if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); + + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + + log_error_write(srv, __FILE__, __LINE__, "sd", "cgi close:", hctx->fd); + + close(hctx->fd); + + cgi_handler_ctx_free(hctx); + + con->plugin_ctx[p->id] = NULL; + + return -1; + } + + break; + } + } + + return 0; +#else + return -1; +#endif +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.assign"))) { + PATCH(cgi); + } + } + } + + return 0; +} + +static int mod_cgi_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(cgi); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(cgi_is_handled) { + size_t k, s_len, i; + plugin_data *p = p_d; + buffer *fn = con->physical.path; + + if (fn->used == 0) return HANDLER_ERROR; + + mod_cgi_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_cgi_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + s_len = fn->used - 1; + + for (k = 0; k < p->conf.cgi->used; k++) { + data_string *ds = (data_string *)p->conf.cgi->data[k]; + size_t ct_len = ds->key->used - 1; + + if (ds->key->used == 0) continue; + if (s_len < ct_len) continue; + + if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) { + if (cgi_create_env(srv, con, p, ds->value)) { + con->http_status = 500; + + buffer_reset(con->physical.path); + } + + return HANDLER_FINISHED; + } + } + + return HANDLER_GO_ON; +} + +TRIGGER_FUNC(cgi_trigger) { + plugin_data *p = p_d; + size_t ndx; + /* the trigger handle only cares about lonely PID which we have to wait for */ +#ifndef __WIN32 + + for (ndx = 0; ndx < p->cgi_pid.used; ndx++) { + int status; + + switch(waitpid(p->cgi_pid.ptr[ndx], &status, WNOHANG)) { + case 0: + /* not finished yet */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", p->cgi_pid.ptr[ndx]); +#endif + break; + case -1: + log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno)); + + return HANDLER_ERROR; + default: + + if (WIFEXITED(status)) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", p->cgi_pid.ptr[ndx]); +#endif + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?"); + } + + cgi_pid_del(srv, p, p->cgi_pid.ptr[ndx]); + /* del modified the buffer structure + * and copies the last entry to the current one + * -> recheck the current index + */ + ndx--; + } + } +#endif + return HANDLER_GO_ON; +} + +SUBREQUEST_FUNC(mod_cgi_handle_subrequest) { + int status; + plugin_data *p = p_d; + handler_ctx *hctx = con->plugin_ctx[p->id]; + + if (con->mode != p->id) return HANDLER_GO_ON; + if (NULL == hctx) return HANDLER_GO_ON; + +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdd", "subrequest, pid =", hctx, hctx->pid); +#endif + if (hctx->pid == 0) return HANDLER_FINISHED; +#ifndef __WIN32 + switch(waitpid(hctx->pid, &status, WNOHANG)) { + case 0: + /* not finished yet */ + if (con->file_started) { + return HANDLER_GO_ON; + } else { + return HANDLER_WAIT_FOR_EVENT; + } + case -1: + if (errno == EINTR) return HANDLER_WAIT_FOR_EVENT; + + if (errno == ECHILD && con->file_started == 0) { + /* + * second round but still not response + */ + return HANDLER_WAIT_FOR_EVENT; + } + + log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno)); + con->mode = DIRECT; + con->http_status = 500; + + hctx->pid = 0; + + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + + if (close(hctx->fd)) { + log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno)); + } + + cgi_handler_ctx_free(hctx); + + con->plugin_ctx[p->id] = NULL; + + return HANDLER_FINISHED; + default: + /* cgi process exited cleanly + * + * check if we already got the response + */ + + if (!con->file_started) return HANDLER_WAIT_FOR_EVENT; + + if (WIFEXITED(status)) { + /* nothing */ + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?"); + + con->mode = DIRECT; + con->http_status = 500; + + } + + hctx->pid = 0; + + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + + if (close(hctx->fd)) { + log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno)); + } + + cgi_handler_ctx_free(hctx); + + con->plugin_ctx[p->id] = NULL; + return HANDLER_FINISHED; + } +#else + return HANDLER_ERROR; +#endif +} + + +int mod_cgi_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("cgi"); + + p->handle_connection_close = cgi_connection_close_callback; + p->handle_subrequest_start = cgi_is_handled; + p->handle_subrequest = mod_cgi_handle_subrequest; +#if 0 + p->handle_fdevent = cgi_handle_fdevent; +#endif + p->handle_trigger = cgi_trigger; + p->init = mod_cgi_init; + p->cleanup = mod_cgi_free; + p->set_defaults = mod_fastcgi_set_defaults; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_compress.c b/src/mod_compress.c new file mode 100644 index 00000000..726d086d --- /dev/null +++ b/src/mod_compress.c @@ -0,0 +1,744 @@ +#include <sys/types.h> +#include <sys/stat.h> + +#include <fcntl.h> +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "response.h" + +#include "plugin.h" + +#include "crc32.h" + +#include "config.h" + +#if defined HAVE_ZLIB_H && defined HAVE_LIBZ +# define USE_ZLIB +# include <zlib.h> +#endif + +#if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2 +# define USE_BZ2LIB +/* we don't need stdio interface */ +# define BZ_NO_STDIO +# include <bzlib.h> +#endif + +#include "sys-mmap.h" + +/* request: accept-encoding */ +#define HTTP_ACCEPT_ENCODING_IDENTITY BV(0) +#define HTTP_ACCEPT_ENCODING_GZIP BV(1) +#define HTTP_ACCEPT_ENCODING_DEFLATE BV(2) +#define HTTP_ACCEPT_ENCODING_COMPRESS BV(3) +#define HTTP_ACCEPT_ENCODING_BZIP2 BV(4) + +#ifdef __WIN32 +#define mkdir(x,y) mkdir(x) +#endif + +typedef struct { + buffer *compress_cache_dir; + array *compress; + off_t compress_max_filesize; /** max filesize in kb */ +} plugin_config; + +typedef struct { + PLUGIN_DATA; + buffer *ofn; + buffer *b; + + plugin_config **config_storage; + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_compress_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->ofn = buffer_init(); + p->b = buffer_init(); + + return p; +} + +FREE_FUNC(mod_compress_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + buffer_free(p->ofn); + buffer_free(p->b); + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->compress); + buffer_free(s->compress_cache_dir); + + free(s); + } + free(p->config_storage); + } + + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_compress_setdefaults) { + plugin_data *p = p_d; + size_t i = 0; + int ret = HANDLER_GO_ON; + + config_values_t cv[] = { + { "compress.cache-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "compress.filetype", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, + { "compress.max-filesize", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->compress_cache_dir = buffer_init(); + s->compress = array_init(); + s->compress_max_filesize = 0; + + cv[0].destination = s->compress_cache_dir; + cv[1].destination = s->compress; + cv[2].destination = &(s->compress_max_filesize); + + p->config_storage[i] = s; + + if (0 != (ret = config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv))) { + ret = HANDLER_ERROR; + + break; + } + + ret = HANDLER_GO_ON; + + if (!buffer_is_empty(s->compress_cache_dir)) { + struct stat st; + if (0 != stat(s->compress_cache_dir->ptr, &st)) { + log_error_write(srv, __FILE__, __LINE__, "sbs", "can't stat compress.cache-dir", + s->compress_cache_dir, strerror(errno)); + + return HANDLER_ERROR; + } + } + } + + return ret; + +} + +#ifdef USE_ZLIB +static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size, time_t mtime) { + unsigned char *c; + unsigned long crc; + z_stream z; + + UNUSED(srv); + UNUSED(con); + + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + + if (Z_OK != deflateInit2(&z, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + -MAX_WBITS, /* supress zlib-header */ + 8, + Z_DEFAULT_STRATEGY)) { + return -1; + } + + z.next_in = start; + z.avail_in = st_size; + z.total_in = 0; + + + buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12 + 18); + + /* write gzip header */ + + c = (unsigned char *)p->b->ptr; + c[0] = 0x1f; + c[1] = 0x8b; + c[2] = Z_DEFLATED; + c[3] = 0; /* options */ + c[4] = (mtime >> 0) & 0xff; + c[5] = (mtime >> 8) & 0xff; + c[6] = (mtime >> 16) & 0xff; + c[7] = (mtime >> 24) & 0xff; + c[8] = 0x00; /* extra flags */ + c[9] = 0x03; /* UNIX */ + + p->b->used = 10; + z.next_out = (unsigned char *)p->b->ptr + p->b->used; + z.avail_out = p->b->size - p->b->used - 8; + z.total_out = 0; + + if (Z_STREAM_END != deflate(&z, Z_FINISH)) { + deflateEnd(&z); + return -1; + } + + /* trailer */ + p->b->used += z.total_out; + + crc = generate_crc32c(start, st_size); + + c = (unsigned char *)p->b->ptr + p->b->used; + + c[0] = (crc >> 0) & 0xff; + c[1] = (crc >> 8) & 0xff; + c[2] = (crc >> 16) & 0xff; + c[3] = (crc >> 24) & 0xff; + c[4] = (z.total_in >> 0) & 0xff; + c[5] = (z.total_in >> 8) & 0xff; + c[6] = (z.total_in >> 16) & 0xff; + c[7] = (z.total_in >> 24) & 0xff; + p->b->used += 8; + + if (Z_OK != deflateEnd(&z)) { + return -1; + } + + return 0; +} + +static int deflate_file_to_buffer_deflate(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) { + z_stream z; + + UNUSED(srv); + UNUSED(con); + + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + z.opaque = Z_NULL; + + if (Z_OK != deflateInit2(&z, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + -MAX_WBITS, /* supress zlib-header */ + 8, + Z_DEFAULT_STRATEGY)) { + return -1; + } + + z.next_in = start; + z.avail_in = st_size; + z.total_in = 0; + + buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12); + + z.next_out = (unsigned char *)p->b->ptr; + z.avail_out = p->b->size; + z.total_out = 0; + + if (Z_STREAM_END != deflate(&z, Z_FINISH)) { + deflateEnd(&z); + return -1; + } + + /* trailer */ + p->b->used += z.total_out; + + if (Z_OK != deflateEnd(&z)) { + return -1; + } + + return 0; +} + +#endif + +#ifdef USE_BZ2LIB +static int deflate_file_to_buffer_bzip2(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) { + bz_stream bz; + + UNUSED(srv); + UNUSED(con); + + bz.bzalloc = NULL; + bz.bzfree = NULL; + bz.opaque = NULL; + + if (BZ_OK != BZ2_bzCompressInit(&bz, + 9, /* blocksize = 900k */ + 0, /* no output */ + 0)) { /* workFactor: default */ + return -1; + } + + bz.next_in = (char *)start; + bz.avail_in = st_size; + bz.total_in_lo32 = 0; + bz.total_in_hi32 = 0; + + buffer_prepare_copy(p->b, (bz.avail_in * 1.1) + 12); + + bz.next_out = p->b->ptr; + bz.avail_out = p->b->size; + bz.total_out_lo32 = 0; + bz.total_out_hi32 = 0; + + if (BZ_STREAM_END != BZ2_bzCompress(&bz, BZ_FINISH)) { + BZ2_bzCompressEnd(&bz); + return -1; + } + + /* file is too large for now */ + if (bz.total_out_hi32) return -1; + + /* trailer */ + p->b->used = bz.total_out_lo32; + + if (BZ_OK != BZ2_bzCompressEnd(&bz)) { + return -1; + } + + return 0; +} +#endif + +static int deflate_file_to_file(server *srv, connection *con, plugin_data *p, buffer *fn, file_cache_entry *fce, int type) { + int ifd, ofd; + int ret = -1; + void *start; + const char *filename = fn->ptr; + ssize_t r; + + /* overflow */ + if ((off_t)(fce->st.st_size * 1.1) < fce->st.st_size) return -1; + + /* don't mmap files > size_t + * + * we could use a sliding window, but currently there is no need for it + */ + + if (fce->st.st_size > SIZE_MAX) return -1; + + buffer_reset(p->ofn); + buffer_copy_string_buffer(p->ofn, p->conf.compress_cache_dir); + BUFFER_APPEND_SLASH(p->ofn); + + if (0 == strncmp(con->physical.path->ptr, con->physical.doc_root->ptr, con->physical.doc_root->used-1)) { + size_t offset = p->ofn->used - 1; + char *dir, *nextdir; + + buffer_append_string(p->ofn, con->physical.path->ptr + con->physical.doc_root->used - 1); + + buffer_copy_string_buffer(p->b, p->ofn); + + /* mkdir -p ... */ + for (dir = p->b->ptr + offset; NULL != (nextdir = strchr(dir, '/')); dir = nextdir + 1) { + *nextdir = '\0'; + + if (-1 == mkdir(p->b->ptr, 0700)) { + if (errno != EEXIST) { + log_error_write(srv, __FILE__, __LINE__, "ssss", "creating cache-directory", p->b->ptr, "failed", strerror(errno)); + + return -1; + } + } + + *nextdir = '/'; + } + } else { + buffer_append_string_buffer(p->ofn, con->uri.path); + } + + switch(type) { + case HTTP_ACCEPT_ENCODING_GZIP: + buffer_append_string(p->ofn, "-gzip-"); + break; + case HTTP_ACCEPT_ENCODING_DEFLATE: + buffer_append_string(p->ofn, "-deflate-"); + break; + case HTTP_ACCEPT_ENCODING_BZIP2: + buffer_append_string(p->ofn, "-bzip2-"); + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "unknown compression type", type); + return -1; + } + + buffer_append_string_buffer(p->ofn, fce->etag); + + if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL, 0600))) { + if (errno == EEXIST) { + /* cache-entry exists */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache hit"); +#endif + buffer_copy_string_buffer(con->physical.path, p->ofn); + + return 0; + } + + log_error_write(srv, __FILE__, __LINE__, "sbss", "creating cachefile", p->ofn, "failed", strerror(errno)); + + return -1; + } +#if 0 + log_error_write(srv, __FILE__, __LINE__, "bs", p->ofn, "compress-cache miss"); +#endif + if (-1 == (ifd = open(filename, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno)); + + close(ofd); + + return -1; + } + + + if (MAP_FAILED == (start = mmap(NULL, fce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno)); + + close(ofd); + close(ifd); + return -1; + } + + switch(type) { +#ifdef USE_ZLIB + case HTTP_ACCEPT_ENCODING_GZIP: + ret = deflate_file_to_buffer_gzip(srv, con, p, start, fce->st.st_size, fce->st.st_mtime); + break; + case HTTP_ACCEPT_ENCODING_DEFLATE: + ret = deflate_file_to_buffer_deflate(srv, con, p, start, fce->st.st_size); + break; +#endif +#ifdef USE_BZ2LIB + case HTTP_ACCEPT_ENCODING_BZIP2: + ret = deflate_file_to_buffer_bzip2(srv, con, p, start, fce->st.st_size); + break; +#endif + default: + ret = -1; + break; + } + + if (-1 == (r = write(ofd, p->b->ptr, p->b->used))) { + return -1; + } + + if ((size_t)r != p->b->used) { + + } + + munmap(start, fce->st.st_size); + close(ofd); + close(ifd); + + if (ret != 0) return -1; + + buffer_copy_string_buffer(con->physical.path, p->ofn); + + return 0; +} + +static int deflate_file_to_buffer(server *srv, connection *con, plugin_data *p, buffer *fn, file_cache_entry *fce, int type) { + int ifd; + int ret = -1; + void *start; + buffer *b; + + /* overflow */ + if ((off_t)(fce->st.st_size * 1.1) < fce->st.st_size) return -1; + + /* don't mmap files > size_t + * + * we could use a sliding window, but currently there is no need for it + */ + + if (fce->st.st_size > SIZE_MAX) return -1; + + + if (-1 == (ifd = open(fn->ptr, O_RDONLY))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "opening plain-file", fn, "failed", strerror(errno)); + + return -1; + } + + + if (MAP_FAILED == (start = mmap(NULL, fce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "sbss", "mmaping", fn, "failed", strerror(errno)); + + close(ifd); + return -1; + } + + switch(type) { +#ifdef USE_ZLIB + case HTTP_ACCEPT_ENCODING_GZIP: + ret = deflate_file_to_buffer_gzip(srv, con, p, start, fce->st.st_size, fce->st.st_mtime); + break; + case HTTP_ACCEPT_ENCODING_DEFLATE: + ret = deflate_file_to_buffer_deflate(srv, con, p, start, fce->st.st_size); + break; +#endif +#ifdef USE_BZ2LIB + case HTTP_ACCEPT_ENCODING_BZIP2: + ret = deflate_file_to_buffer_bzip2(srv, con, p, start, fce->st.st_size); + break; +#endif + default: + ret = -1; + break; + } + + munmap(start, fce->st.st_size); + close(ifd); + + if (ret != 0) return -1; + + chunkqueue_reset(con->write_queue); + b = chunkqueue_get_append_buffer(con->write_queue); + buffer_copy_memory(b, p->b->ptr, p->b->used); + + buffer_reset(con->physical.path); + + con->file_finished = 1; + con->file_started = 1; + + return 0; +} + + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_compress_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.cache-dir"))) { + PATCH(compress_cache_dir); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.filetype"))) { + PATCH(compress); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.max-filesize"))) { + PATCH(compress_max_filesize); + } + } + } + + return 0; +} + +static int mod_compress_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(compress_cache_dir); + PATCH(compress); + PATCH(compress_max_filesize); + + return 0; +} +#undef PATCH + +PHYSICALPATH_FUNC(mod_compress_physical) { + plugin_data *p = p_d; + data_string *content_ds; + size_t m, i; + off_t max_fsize; + + /* only GET and POST can get compressed */ + if (con->request.http_method != HTTP_METHOD_GET && + con->request.http_method != HTTP_METHOD_POST) { + return HANDLER_GO_ON; + } + + mod_compress_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_compress_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + + max_fsize = p->conf.compress_max_filesize; + + /* don't compress files that are too large as we need to much time to handle them */ + if (max_fsize && (con->fce->st.st_size >> 10) > max_fsize) return HANDLER_GO_ON; + + if (NULL == (content_ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { + log_error_write(srv, __FILE__, __LINE__, "sbb", "Content-Type is not set for", con->physical.path, con->uri.path); + + return HANDLER_GO_ON; + } + + /* check if mimetype is in compress-config */ + for (m = 0; m < p->conf.compress->used; m++) { + data_string *compress_ds = (data_string *)p->conf.compress->data[m]; + + if (!compress_ds) { + log_error_write(srv, __FILE__, __LINE__, "sbb", "evil", con->physical.path, con->uri.path); + + return HANDLER_GO_ON; + } + + if (buffer_is_equal(compress_ds->value, content_ds->value)) { + /* mimetype found */ + data_string *ds; + + /* the response might change according to Accept-Encoding */ + response_header_insert(srv, con, CONST_STR_LEN("Vary"), CONST_STR_LEN("Accept-Encoding")); + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Accept-Encoding"))) { + int accept_encoding = 0; + char *value = ds->value->ptr; + int srv_encodings = 0; + int matched_encodings = 0; + + /* get client side support encodings */ + if (NULL != strstr(value, "gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP; + if (NULL != strstr(value, "deflate")) accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE; + if (NULL != strstr(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS; + if (NULL != strstr(value, "bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2; + if (NULL != strstr(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY; + + /* get server side supported ones */ +#ifdef USE_BZ2LIB + srv_encodings |= HTTP_ACCEPT_ENCODING_BZIP2; +#endif +#ifdef USE_ZLIB + srv_encodings |= HTTP_ACCEPT_ENCODING_GZIP; + srv_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE; +#endif + + /* find matching entries */ + matched_encodings = accept_encoding & srv_encodings; + + if (matched_encodings) { + const char *dflt_gzip = "gzip"; + const char *dflt_deflate = "deflate"; + const char *dflt_bzip2 = "bzip2"; + + const char *compression_name = NULL; + int compression_type = 0; + + /* select best matching encoding */ + if (matched_encodings & HTTP_ACCEPT_ENCODING_BZIP2) { + compression_type = HTTP_ACCEPT_ENCODING_BZIP2; + compression_name = dflt_bzip2; + } else if (matched_encodings & HTTP_ACCEPT_ENCODING_GZIP) { + compression_type = HTTP_ACCEPT_ENCODING_GZIP; + compression_name = dflt_gzip; + } else if (matched_encodings & HTTP_ACCEPT_ENCODING_DEFLATE) { + compression_type = HTTP_ACCEPT_ENCODING_DEFLATE; + compression_name = dflt_deflate; + } + + /* deflate it */ + if (p->conf.compress_cache_dir->used) { + if (0 == deflate_file_to_file(srv, con, p, + con->physical.path, con->fce, compression_type)) { + struct tm *tm; + time_t last_mod; + + response_header_insert(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name)); + + /* Set Last-Modified of ORIGINAL file */ + last_mod = con->fce->st.st_mtime; + + for (i = 0; i < FILE_CACHE_MAX; i++) { + if (srv->mtime_cache[i].mtime == last_mod) break; + + if (srv->mtime_cache[i].mtime == 0) { + srv->mtime_cache[i].mtime = last_mod; + + buffer_prepare_copy(srv->mtime_cache[i].str, 1024); + + tm = gmtime(&(srv->mtime_cache[i].mtime)); + srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, + srv->mtime_cache[i].str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", tm); + + srv->mtime_cache[i].str->used++; + break; + } + } + + if (i == FILE_CACHE_MAX) { + i = 0; + + srv->mtime_cache[i].mtime = last_mod; + buffer_prepare_copy(srv->mtime_cache[i].str, 1024); + tm = gmtime(&(srv->mtime_cache[i].mtime)); + srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, + srv->mtime_cache[i].str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", tm); + srv->mtime_cache[i].str->used++; + } + + response_header_insert(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(srv->mtime_cache[i].str)); + + return HANDLER_FINISHED; + } + } else if (0 == deflate_file_to_buffer(srv, con, p, + con->physical.path, con->fce, compression_type)) { + + response_header_insert(srv, con, CONST_STR_LEN("Content-Encoding"), compression_name, strlen(compression_name)); + + return HANDLER_FINISHED; + } + break; + } + } + } + } + + return HANDLER_GO_ON; +} + +int mod_compress_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("compress"); + + p->init = mod_compress_init; + p->set_defaults = mod_compress_setdefaults; + p->handle_physical_path = mod_compress_physical; + p->cleanup = mod_compress_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_evhost.c b/src/mod_evhost.c new file mode 100644 index 00000000..4a07a923 --- /dev/null +++ b/src/mod_evhost.c @@ -0,0 +1,340 @@ +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include "plugin.h" +#include "log.h" +#include "response.h" +#include "file_cache.h" + +typedef struct { + /* unparsed pieces */ + buffer *path_pieces_raw; + + /* pieces for path creation */ + size_t len; + buffer **path_pieces; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + buffer *tmp_buf; + + plugin_config **config_storage; + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_evhost_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + return p; +} + +FREE_FUNC(mod_evhost_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if(s->path_pieces) { + for (i = 0; i < s->len; i++) { + buffer_free(s->path_pieces[i]); + } + + free(s->path_pieces); + } + + buffer_free(s->path_pieces_raw); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +static void mod_evhost_parse_pattern(plugin_config *s) { + char *ptr = s->path_pieces_raw->ptr,*pos; + + s->path_pieces = NULL; + + for(pos=ptr;*ptr;ptr++) { + if(*ptr == '%') { + s->path_pieces = realloc(s->path_pieces,(s->len+2) * sizeof(*s->path_pieces)); + s->path_pieces[s->len] = buffer_init(); + s->path_pieces[s->len+1] = buffer_init(); + + buffer_copy_string_len(s->path_pieces[s->len],pos,ptr-pos); + pos = ptr + 2; + + buffer_copy_string_len(s->path_pieces[s->len+1],ptr++,2); + + s->len += 2; + } + } + + if(*pos != '\0') { + s->path_pieces = realloc(s->path_pieces,(s->len+1) * sizeof(*s->path_pieces)); + s->path_pieces[s->len] = buffer_init(); + + buffer_append_memory(s->path_pieces[s->len],pos,ptr-pos); + + s->len += 1; + } +} + +SETDEFAULTS_FUNC(mod_evhost_set_defaults) { + plugin_data *p = p_d; + size_t i; + + /** + * + * # + * # define a pattern for the host url finding + * # %% => % sign + * # %0 => domain name + tld + * # %1 => tld + * # %2 => domain name without tld + * # %3 => subdomain 1 name + * # %4 => subdomain 2 name + * # + * evhost.path-pattern = "/home/ckruse/dev/www/%3/htdocs/" + * + */ + + config_values_t cv[] = { + { "evhost.path-pattern", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + s->path_pieces_raw = buffer_init(); + s->path_pieces = NULL; + s->len = 0; + + cv[0].destination = s->path_pieces_raw; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + + if (s->path_pieces_raw->used != 0) { + mod_evhost_parse_pattern(s); + } + } + + return HANDLER_GO_ON; +} + +/** + * assign the different parts of the domain to array-indezes + * - %0 - full hostname (authority w/o port) + * - %1 - tld + * - %2 - domain.tld + * - %3 - + */ + +static int mod_evhost_parse_host(connection *con,array *host) { + /* con->uri.authority->used is always > 0 if we come here */ + register char *ptr = con->uri.authority->ptr + con->uri.authority->used - 1; + char *colon = ptr; /* needed to filter out the colon (if exists) */ + int first = 1; + data_string *ds; + int i; + + /* first, find the domain + tld */ + for(;ptr > con->uri.authority->ptr;ptr--) { + if(*ptr == '.') { + if(first) first = 0; + else break; + } else if(*ptr == ':') { + colon = ptr; + first = 1; + } + } + + ds = data_string_init(); + buffer_copy_string(ds->key,"%0"); + + /* if we stopped at a dot, skip the dot */ + if (*ptr == '.') ptr++; + buffer_copy_string_len(ds->value, ptr, colon-ptr); + + array_insert_unique(host,(data_unset *)ds); + + /* if the : is not the start of the authority, go on parsing the hostname */ + + if (colon != con->uri.authority->ptr) { + for(ptr = colon - 1, i = 1; ptr > con->uri.authority->ptr; ptr--) { + if(*ptr == '.') { + if (ptr != colon - 1) { + /* is something between the dots */ + ds = data_string_init(); + buffer_copy_string(ds->key,"%"); + buffer_append_long(ds->key, i++); + buffer_copy_string_len(ds->value,ptr+1,colon-ptr-1); + + array_insert_unique(host,(data_unset *)ds); + } + colon = ptr; + } + } + + /* if the . is not the first charactor of the hostname */ + if (colon != ptr) { + ds = data_string_init(); + buffer_copy_string(ds->key,"%"); + buffer_append_long(ds->key, i++); + buffer_copy_string_len(ds->value,ptr,colon-ptr); + + array_insert_unique(host,(data_unset *)ds); + } + } + + return 0; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_evhost_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("evhost.path-pattern"))) { + PATCH(path_pieces); + PATCH(len); + } + } + } + + return 0; +} + +static int mod_evhost_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(path_pieces); + PATCH(len); + + return 0; +} +#undef PATCH + + +static handler_t mod_evhost_uri_handler(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + size_t i; + array *parsed_host; + register char *ptr; + int not_good = 0; + + /* not authority set */ + if (con->uri.authority->used == 0) return HANDLER_GO_ON; + + mod_evhost_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_evhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + parsed_host = array_init(); + + mod_evhost_parse_host(con, parsed_host); + + /* build document-root */ + buffer_reset(p->tmp_buf); + + for (i = 0; i < p->conf.len; i++) { + ptr = p->conf.path_pieces[i]->ptr; + if (*ptr == '%') { + data_string *ds; + + if (*(ptr+1) == '%') { + /* %% */ + BUFFER_APPEND_STRING_CONST(p->tmp_buf,"%"); + } else if (NULL != (ds = (data_string *)array_get_element(parsed_host,p->conf.path_pieces[i]->ptr))) { + if (ds->value->used) { + buffer_append_string_buffer(p->tmp_buf,ds->value); + } + } else { + /* unhandled %-sequence */ + } + } else { + buffer_append_string_buffer(p->tmp_buf,p->conf.path_pieces[i]); + } + } + + BUFFER_APPEND_SLASH(p->tmp_buf); + + array_free(parsed_host); + + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, p->tmp_buf, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf); + not_good = 1; + } else if(!S_ISDIR(con->fce->st.st_mode)) { + log_error_write(srv, __FILE__, __LINE__, "sb", "not a directory:", p->tmp_buf); + not_good = 1; + } + + if (!not_good) { + buffer_copy_string_buffer(con->physical.doc_root, p->tmp_buf); + } + + return HANDLER_GO_ON; +} + +int mod_evhost_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("evhost"); + p->init = mod_evhost_init; + p->set_defaults = mod_evhost_set_defaults; + p->handle_docroot = mod_evhost_uri_handler; + p->cleanup = mod_evhost_free; + + p->data = NULL; + + return 0; +} + +/* eof */ diff --git a/src/mod_expire.c b/src/mod_expire.c new file mode 100644 index 00000000..e22e2a32 --- /dev/null +++ b/src/mod_expire.c @@ -0,0 +1,356 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "response.h" + +#include "plugin.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/** + * this is a expire module for a lighttpd + * + * set 'Expires:' HTTP Headers on demand + */ + + + +/* plugin config for all request/connections */ + +typedef struct { + array *expire_url; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *expire_tstmp; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_expire_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->expire_tstmp = buffer_init(); + + buffer_prepare_copy(p->expire_tstmp, 255); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_expire_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + buffer_free(p->expire_tstmp); + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->expire_url); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +static int mod_expire_get_offset(server *srv, plugin_data *p, buffer *expire, int *offset) { + char *ts; + int type = -1; + int retts = 0; + + UNUSED(p); + + /* + * parse + * + * '(access|modification) [plus] {<num> <type>}*' + * + * e.g. 'access 1 years' + */ + + if (expire->used == 0) { + log_error_write(srv, __FILE__, __LINE__, "s", + "empty:"); + return -1; + } + + ts = expire->ptr; + + if (0 == strncmp(ts, "access ", 7)) { + type = 0; + ts += 7; + } else if (0 == strncmp(ts, "modification ", 13)) { + type = 0; + ts += 13; + } else { + /* invalid type-prefix */ + log_error_write(srv, __FILE__, __LINE__, "ss", + "invalid <base>:", ts); + return -1; + } + + if (0 == strncmp(ts, "plus ", 5)) { + /* skip the optional plus */ + ts += 5; + } + + /* the rest is just <number> (years|months|days|hours|minutes|seconds) */ + while (1) { + char *space, *err; + int num; + + if (NULL == (space = strchr(ts, ' '))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "missing space after <num>:", ts); + return -1; + } + + num = strtol(ts, &err, 10); + if (*err != ' ') { + log_error_write(srv, __FILE__, __LINE__, "ss", + "missing <type> after <num>:", ts); + return -1; + } + + ts = space + 1; + + if (NULL != (space = strchr(ts, ' '))) { + int slen; + /* */ + + slen = space - ts; + + if (slen == 5 && + 0 == strncmp(ts, "years", slen)) { + num *= 60 * 60 * 24 * 30 * 12; + } else if (slen == 6 && + 0 == strncmp(ts, "months", slen)) { + num *= 60 * 60 * 24 * 30; + } else if (slen == 4 && + 0 == strncmp(ts, "days", slen)) { + num *= 60 * 60 * 24; + } else if (slen == 5 && + 0 == strncmp(ts, "hours", slen)) { + num *= 60 * 60; + } else if (slen == 7 && + 0 == strncmp(ts, "minutes", slen)) { + num *= 60; + } else if (slen == 7 && + 0 == strncmp(ts, "seconds", slen)) { + num *= 1; + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", + "unknown type:", ts); + return -1; + } + + retts += num; + + ts = space + 1; + } else { + if (0 == strcmp(ts, "years")) { + num *= 60 * 60 * 24 * 30 * 12; + } else if (0 == strcmp(ts, "months")) { + num *= 60 * 60 * 24 * 30; + } else if (0 == strcmp(ts, "days")) { + num *= 60 * 60 * 24; + } else if (0 == strcmp(ts, "hours")) { + num *= 60 * 60; + } else if (0 == strcmp(ts, "minutes")) { + num *= 60; + } else if (0 == strcmp(ts, "seconds")) { + num *= 1; + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", + "unknown type:", ts); + return -1; + } + + retts += num; + + break; + } + } + + if (offset != NULL) *offset = retts; + + return type; +} + + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_expire_set_defaults) { + plugin_data *p = p_d; + size_t i = 0, k; + + config_values_t cv[] = { + { "expire.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->expire_url = array_init(); + + cv[0].destination = s->expire_url; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + + for (k = 0; k < s->expire_url->used; k++) { + data_string *ds = (data_string *)s->expire_url->data[k]; + + /* parse lines */ + if (-1 == mod_expire_get_offset(srv, p, ds->value, NULL)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "parsing expire.url failed:", ds->value); + return HANDLER_ERROR; + } + } + } + + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_expire_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("expire.url"))) { + PATCH(expire_url); + } + } + } + + return 0; +} + +static int mod_expire_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(expire_url); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_expire_uri_handler) { + plugin_data *p = p_d; + int s_len; + size_t k, i; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_expire_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_expire_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + s_len = con->uri.path->used - 1; + + for (k = 0; k < p->conf.expire_url->used; k++) { + data_string *ds = (data_string *)p->conf.expire_url->data[k]; + int ct_len = ds->key->used - 1; + + if (ct_len > s_len) continue; + if (ds->key->used == 0) continue; + + if (0 == strncmp(con->uri.path->ptr, ds->key->ptr, ct_len)) { + int ts; + time_t t; + size_t len; + + mod_expire_get_offset(srv, p, ds->value, &ts); + t = (ts += srv->cur_ts); + + if (0 == (len = strftime(p->expire_tstmp->ptr, p->expire_tstmp->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(t))))) { + /* could not set expire header, out of mem */ + + return HANDLER_GO_ON; + + } + + p->expire_tstmp->used = len + 1; + + response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_BUF_LEN(p->expire_tstmp)); + + return HANDLER_GO_ON; + } + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_expire_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("expire"); + + p->init = mod_expire_init; + p->handle_uri_clean = mod_expire_uri_handler; + p->set_defaults = mod_expire_set_defaults; + p->cleanup = mod_expire_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c new file mode 100644 index 00000000..5438e6fa --- /dev/null +++ b/src/mod_fastcgi.c @@ -0,0 +1,3209 @@ +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <assert.h> +#include <signal.h> + +#include "buffer.h" +#include "server.h" +#include "keyvalue.h" +#include "log.h" + +#include "http_chunk.h" +#include "fdevent.h" +#include "connections.h" +#include "response.h" +#include "joblist.h" + +#include "plugin.h" + +#include "inet_ntop_cache.h" + +#include <fastcgi.h> +#include <stdio.h> + +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#include "sys-socket.h" + + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 +#endif + +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + + +/* + * + * TODO: + * + * - add timeout for a connect to a non-fastcgi process + * (use state_timestamp + state) + * + */ + +typedef struct fcgi_proc { + size_t id; /* id will be between 1 and max_procs */ + buffer *socket; /* config.socket + "-" + id */ + unsigned port; /* config.port + pno */ + + pid_t pid; /* PID of the spawned process (0 if not spawned locally) */ + + + size_t load; /* number of requests waiting on this process */ + + time_t last_used; /* see idle_timeout */ + size_t requests; /* see max_requests */ + struct fcgi_proc *prev, *next; /* see first */ + + time_t disable_ts; /* replace by host->something */ + + int is_local; + + enum { PROC_STATE_UNSET, /* init-phase */ + PROC_STATE_RUNNING, /* alive */ + PROC_STATE_DIED_WAIT_FOR_PID, + PROC_STATE_KILLED, /* was killed as we don't have the load anymore */ + PROC_STATE_DIED, /* marked as dead, should be restarted */ + PROC_STATE_DISABLED /* proc disabled as it resulted in an error */ + } state; +} fcgi_proc; + +typedef struct { + /* list of processes handling this extension + * sorted by lowest load + * + * whenever a job is done move it up in the list + * until it is sorted, move it down as soon as the + * job is started + */ + fcgi_proc *first; + fcgi_proc *unused_procs; + + /* + * spawn at least min_procs, at max_procs. + * + * as soon as the load of the first entry + * is max_load_per_proc we spawn a new one + * and add it to the first entry and give it + * the load + * + */ + + unsigned short min_procs; + unsigned short max_procs; + size_t num_procs; /* how many procs are started */ + size_t active_procs; /* how many of them are really running */ + + unsigned short max_load_per_proc; + + /* + * kick the process from the list if it was not + * used for idle_timeout until min_procs is + * reached. this helps to get the processlist + * small again we had a small peak load. + * + */ + + unsigned short idle_timeout; + + /* + * time after a disabled remote connection is tried to be re-enabled + * + * + */ + + unsigned short disable_time; + + /* + * same fastcgi processes get a little bit larger + * than wanted. max_requests_per_proc kills a + * process after a number of handled requests. + * + */ + size_t max_requests_per_proc; + + + /* config */ + + /* + * host:port + * + * if host is one of the local IP adresses the + * whole connection is local + * + * if tcp/ip should be used host AND port have + * to be specified + * + */ + buffer *host; + unsigned short port; + + /* + * Unix Domain Socket + * + * instead of TCP/IP we can use Unix Domain Sockets + * - more secure (you have fileperms to play with) + * - more control (on locally) + * - more speed (no extra overhead) + */ + buffer *unixsocket; + + /* if socket is local we can start the fastcgi + * process ourself + * + * bin-path is the path to the binary + * + * check min_procs and max_procs for the number + * of process to start-up + */ + buffer *bin_path; + + /* bin-path is set bin-environment is taken to + * create the environement before starting the + * FastCGI process + * + */ + array *bin_env; + + array *bin_env_copy; + + /* + * docroot-translation between URL->phys and the + * remote host + * + * reasons: + * - different dir-layout if remote + * - chroot if local + * + */ + buffer *docroot; + + /* + * fastcgi-mode: + * - responser + * - authorizer + * + */ + unsigned short mode; + + /* + * check_local tell you if the phys file is stat()ed + * or not. FastCGI doesn't care if the service is + * remote. If the web-server side doesn't contain + * the fastcgi-files we should not stat() for them + * and say '404 not found'. + */ + unsigned short check_local; + + + ssize_t load; /* replace by host->load */ + + size_t max_id; /* corresponds most of the time to + num_procs. + + only if a process is killed max_id waits for the process itself + to die and decrements its afterwards */ +} fcgi_extension_host; + +/* + * one extension can have multiple hosts assigned + * one host can spawn additional processes on the same + * socket (if we control it) + * + * ext -> host -> procs + * 1:n 1:n + * + * if the fastcgi process is remote that whole goes down + * to + * + * ext -> host -> procs + * 1:n 1:1 + * + * in case of PHP and FCGI_CHILDREN we have again a procs + * but we don't control it directly. + * + */ + +typedef struct { + buffer *key; /* like .php */ + + fcgi_extension_host **hosts; + + size_t used; + size_t size; +} fcgi_extension; + +typedef struct { + fcgi_extension **exts; + + size_t used; + size_t size; +} fcgi_exts; + + +typedef struct { + fcgi_exts *exts; + + int debug; +} plugin_config; + +typedef struct { + size_t *ptr; + size_t used; + size_t size; +} buffer_uint; + +typedef struct { + char **ptr; + + size_t size; + size_t used; +} char_array; + +/* generic plugin data, shared between all connections */ +typedef struct { + PLUGIN_DATA; + buffer_uint fcgi_request_id; + + buffer *fcgi_env; + + buffer *path; + buffer *parse_response; + + plugin_config **config_storage; + + plugin_config conf; /* this is only used as long as no handler_ctx is setup */ +} plugin_data; + +/* connection specific data */ +typedef enum { FCGI_STATE_INIT, FCGI_STATE_CONNECT, FCGI_STATE_PREPARE_WRITE, + FCGI_STATE_WRITE, FCGI_STATE_READ +} fcgi_connection_state_t; + +typedef struct { + buffer *response; + size_t response_len; + int response_type; + int response_padding; + size_t response_request_id; + + fcgi_proc *proc; + fcgi_extension_host *host; + + fcgi_connection_state_t state; + time_t state_timestamp; + + int reconnects; /* number of reconnect attempts */ + + buffer *write_buffer; + size_t write_offset; + + read_buffer *rb; + + buffer *response_header; + + int delayed; /* flag to mark that the connect() is delayed */ + + size_t request_id; + int fd; /* fd to the fastcgi process */ + int fde_ndx; /* index into the fd-event buffer */ + + size_t path_info_offset; /* start of path_info in uri.path */ + + plugin_config conf; + + connection *remote_conn; /* dumb pointer */ + plugin_data *plugin_data; /* dumb pointer */ +} handler_ctx; + + +/* ok, we need a prototype */ +static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents); + +int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc); + + + +static handler_ctx * handler_ctx_init() { + handler_ctx * hctx; + + hctx = calloc(1, sizeof(*hctx)); + assert(hctx); + + hctx->fde_ndx = -1; + + hctx->response = buffer_init(); + hctx->response_header = buffer_init(); + hctx->write_buffer = buffer_init(); + + hctx->request_id = 0; + hctx->state = FCGI_STATE_INIT; + hctx->proc = NULL; + + hctx->response_len = 0; + hctx->response_type = 0; + hctx->response_padding = 0; + hctx->response_request_id = 0; + hctx->fd = -1; + + hctx->reconnects = 0; + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + buffer_free(hctx->response); + buffer_free(hctx->response_header); + buffer_free(hctx->write_buffer); + + if (hctx->rb) { + if (hctx->rb->ptr) free(hctx->rb->ptr); + free(hctx->rb); + } + + free(hctx); +} + +fcgi_proc *fastcgi_process_init() { + fcgi_proc *f; + + f = calloc(1, sizeof(*f)); + f->socket = buffer_init(); + + f->prev = NULL; + f->next = NULL; + + return f; +} + +void fastcgi_process_free(fcgi_proc *f) { + if (!f) return; + + fastcgi_process_free(f->next); + + buffer_free(f->socket); + + free(f); +} + +fcgi_extension_host *fastcgi_host_init() { + fcgi_extension_host *f; + + f = calloc(1, sizeof(*f)); + + f->host = buffer_init(); + f->unixsocket = buffer_init(); + f->docroot = buffer_init(); + f->bin_path = buffer_init(); + f->bin_env = array_init(); + f->bin_env_copy = array_init(); + + return f; +} + +void fastcgi_host_free(fcgi_extension_host *h) { + if (!h) return; + + buffer_free(h->host); + buffer_free(h->unixsocket); + buffer_free(h->docroot); + buffer_free(h->bin_path); + array_free(h->bin_env); + array_free(h->bin_env_copy); + + fastcgi_process_free(h->first); + fastcgi_process_free(h->unused_procs); + + free(h); + +} + +fcgi_exts *fastcgi_extensions_init() { + fcgi_exts *f; + + f = calloc(1, sizeof(*f)); + + return f; +} + +void fastcgi_extensions_free(fcgi_exts *f) { + size_t i; + + if (!f) return; + + for (i = 0; i < f->used; i++) { + fcgi_extension *fe; + size_t j; + + fe = f->exts[i]; + + for (j = 0; j < fe->used; j++) { + fcgi_extension_host *h; + + h = fe->hosts[j]; + + fastcgi_host_free(h); + } + + buffer_free(fe->key); + free(fe->hosts); + + free(fe); + } + + free(f->exts); + + free(f); +} + +int fastcgi_extension_insert(fcgi_exts *ext, buffer *key, fcgi_extension_host *fh) { + fcgi_extension *fe; + size_t i; + + /* there is something */ + + for (i = 0; i < ext->used; i++) { + if (buffer_is_equal(key, ext->exts[i]->key)) { + break; + } + } + + if (i == ext->used) { + /* filextension is new */ + fe = calloc(1, sizeof(*fe)); + assert(fe); + fe->key = buffer_init(); + buffer_copy_string_buffer(fe->key, key); + + /* */ + + if (ext->size == 0) { + ext->size = 8; + ext->exts = malloc(ext->size * sizeof(*(ext->exts))); + assert(ext->exts); + } else if (ext->used == ext->size) { + ext->size += 8; + ext->exts = realloc(ext->exts, ext->size * sizeof(*(ext->exts))); + assert(ext->exts); + } + ext->exts[ext->used++] = fe; + } else { + fe = ext->exts[i]; + } + + if (fe->size == 0) { + fe->size = 4; + fe->hosts = malloc(fe->size * sizeof(*(fe->hosts))); + assert(fe->hosts); + } else if (fe->size == fe->used) { + fe->size += 4; + fe->hosts = realloc(fe->hosts, fe->size * sizeof(*(fe->hosts))); + assert(fe->hosts); + } + + fe->hosts[fe->used++] = fh; + + return 0; + +} + +INIT_FUNC(mod_fastcgi_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->fcgi_env = buffer_init(); + + p->path = buffer_init(); + p->parse_response = buffer_init(); + + return p; +} + + +FREE_FUNC(mod_fastcgi_free) { + plugin_data *p = p_d; + buffer_uint *r = &(p->fcgi_request_id); + + UNUSED(srv); + + if (r->ptr) free(r->ptr); + + buffer_free(p->fcgi_env); + buffer_free(p->path); + buffer_free(p->parse_response); + + if (p->config_storage) { + size_t i, j, n; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + fcgi_exts *exts; + + if (!s) continue; + + exts = s->exts; + + for (j = 0; j < exts->used; j++) { + fcgi_extension *ex; + + ex = exts->exts[j]; + + for (n = 0; n < ex->used; n++) { + fcgi_proc *proc; + fcgi_extension_host *host; + + host = ex->hosts[n]; + + for (proc = host->first; proc; proc = proc->next) { + if (proc->pid != 0) kill(proc->pid, SIGTERM); + + if (proc->is_local && + !buffer_is_empty(proc->socket)) { + unlink(proc->socket->ptr); + } + } + + for (proc = host->unused_procs; proc; proc = proc->next) { + if (proc->pid != 0) kill(proc->pid, SIGTERM); + + if (proc->is_local && + !buffer_is_empty(proc->socket)) { + unlink(proc->socket->ptr); + } + } + } + } + + fastcgi_extensions_free(s->exts); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +static int env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) { + char *dst; + + if (!key || !val) return -1; + + dst = malloc(key_len + val_len + 3); + memcpy(dst, key, key_len); + dst[key_len] = '='; + /* add the \0 from the value */ + memcpy(dst + key_len + 1, val, val_len + 1); + + if (env->size == 0) { + env->size = 16; + env->ptr = malloc(env->size * sizeof(*env->ptr)); + } else if (env->size == env->used) { + env->size += 16; + env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr)); + } + + env->ptr[env->used++] = dst; + + return 0; +} + +static int fcgi_spawn_connection(server *srv, + plugin_data *p, + fcgi_extension_host *host, + fcgi_proc *proc) { + int fcgi_fd; + int socket_type, status; + struct timeval tv = { 0, 100 * 1000 }; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un fcgi_addr_un; +#endif + struct sockaddr_in fcgi_addr_in; + struct sockaddr *fcgi_addr; + + socklen_t servlen; + +#ifndef HAVE_FORK + return -1; +#endif + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sdb", + "new proc, socket:", proc->port, proc->socket); + } + + if (!buffer_is_empty(proc->socket)) { + memset(&fcgi_addr, 0, sizeof(fcgi_addr)); + +#ifdef HAVE_SYS_UN_H + fcgi_addr_un.sun_family = AF_UNIX; + strcpy(fcgi_addr_un.sun_path, proc->socket->ptr); + +#ifdef SUN_LEN + servlen = SUN_LEN(&fcgi_addr_un); +#else + /* stevens says: */ + servlen = proc->socket - 1 + sizeof(fcgi_addr_un.sun_family); +#endif + socket_type = AF_UNIX; + fcgi_addr = (struct sockaddr *) &fcgi_addr_un; +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: Unix Domain sockets are not supported."); + return -1; +#endif + } else { + fcgi_addr_in.sin_family = AF_INET; + + if (buffer_is_empty(host->host)) { + fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + struct hostent *he; + + /* set a usefull default */ + fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + + + if (NULL == (he = gethostbyname(host->host->ptr))) { + log_error_write(srv, __FILE__, __LINE__, + "ssb", "gethostbyname failed: ", + hstrerror(h_errno), host->host); + return -1; + } + + if (he->h_addrtype != AF_INET) { + log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype); + return -1; + } + + if (he->h_length != sizeof(struct in_addr)) { + log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length); + return -1; + } + + memcpy(&(fcgi_addr_in.sin_addr.s_addr), he->h_addr_list[0], he->h_length); + + } + fcgi_addr_in.sin_port = htons(proc->port); + servlen = sizeof(fcgi_addr_in); + + socket_type = AF_INET; + fcgi_addr = (struct sockaddr *) &fcgi_addr_in; + } + + if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "failed:", strerror(errno)); + return -1; + } + + if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) { + /* server is not up, spawn in */ + pid_t child; + int val; + + if (!buffer_is_empty(proc->socket)) { + unlink(proc->socket->ptr); + } + + close(fcgi_fd); + + /* reopen socket */ + if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "socket failed:", strerror(errno)); + return -1; + } + + val = 1; + if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "socketsockopt failed:", strerror(errno)); + return -1; + } + + /* create socket */ + if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "bind failed:", strerror(errno)); + return -1; + } + + if (-1 == listen(fcgi_fd, 1024)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "listen failed:", strerror(errno)); + return -1; + } + +#ifdef HAVE_FORK + switch ((child = fork())) { + case 0: { + buffer *b; + size_t i = 0; + char_array env; + + + /* create environment */ + env.ptr = NULL; + env.size = 0; + env.used = 0; + + if(fcgi_fd != FCGI_LISTENSOCK_FILENO) { + close(FCGI_LISTENSOCK_FILENO); + dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO); + close(fcgi_fd); + } + + /* we don't need the client socket */ + for (i = 3; i < 256; i++) { + close(i); + } + + /* build clean environment */ + if (host->bin_env_copy->used) { + for (i = 0; i < host->bin_env_copy->used; i++) { + data_string *ds = (data_string *)host->bin_env_copy->data[i]; + char *ge; + + if (NULL != (ge = getenv(ds->value->ptr))) { + env_add(&env, CONST_BUF_LEN(ds->value), ge, strlen(ge)); + } + } + } else { + for (i = 0; environ[i]; i++) { + char *eq; + + if (NULL != (eq = strchr(environ[i], '='))) { + env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1)); + } + } + } + + /* create environment */ + for (i = 0; i < host->bin_env->used; i++) { + data_string *ds = (data_string *)host->bin_env->data[i]; + + env_add(&env, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value)); + } + + for (i = 0; i < env.used; i++) { + /* search for PHP_FCGI_CHILDREN */ + if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=", sizeof("PHP_FCGI_CHILDREN=") - 1)) break; + } + + /* not found, add a default */ + if (i == env.used) { + env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"), CONST_STR_LEN("1")); + } + + env.ptr[env.used] = NULL; + + b = buffer_init(); + buffer_copy_string(b, "exec "); + buffer_append_string_buffer(b, host->bin_path); + + /* exec the cgi */ + execle("/bin/sh", "sh", "-c", b->ptr, NULL, env.ptr); + + log_error_write(srv, __FILE__, __LINE__, "sbs", + "execl failed for:", host->bin_path, strerror(errno)); + + exit(errno); + + break; + } + case -1: + /* error */ + break; + default: + /* father */ + + /* wait */ + select(0, NULL, NULL, NULL, &tv); + + switch (waitpid(child, &status, WNOHANG)) { + case 0: + /* child still running after timeout, good */ + break; + case -1: + /* no PID found ? should never happen */ + log_error_write(srv, __FILE__, __LINE__, "ss", + "pid not found:", strerror(errno)); + return -1; + default: + /* the child should not terminate at all */ + if (WIFEXITED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child exited (is this a FastCGI binary ?):", + WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child signaled:", + WTERMSIG(status)); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child died somehow:", + status); + } + return -1; + } + + /* register process */ + proc->pid = child; + proc->last_used = srv->cur_ts; + proc->is_local = 1; + + break; + } +#endif + } else { + proc->is_local = 0; + proc->pid = 0; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "(debug) socket is already used, won't spawn:", + proc->socket); + } + } + + proc->state = PROC_STATE_RUNNING; + host->active_procs++; + + close(fcgi_fd); + + return 0; +} + + +SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { + plugin_data *p = p_d; + data_unset *du; + size_t i = 0; + buffer *fcgi_mode = buffer_init(); + + config_values_t cv[] = { + { "fastcgi.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "fastcgi.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + array *ca; + + s = malloc(sizeof(plugin_config)); + s->exts = fastcgi_extensions_init(); + s->debug = 0; + + cv[0].destination = s->exts; + cv[1].destination = &(s->debug); + + p->config_storage[i] = s; + ca = ((data_config *)srv->config_context->data[i])->value; + + if (0 != config_insert_values_global(srv, ca, cv)) { + return HANDLER_ERROR; + } + + /* + * <key> = ( ... ) + */ + + if (NULL != (du = array_get_element(ca, "fastcgi.server"))) { + size_t j; + data_array *da = (data_array *)du; + + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", "fastcgi.server", "array of strings"); + + return HANDLER_ERROR; + } + + + /* + * fastcgi.server = ( "<ext>" => ( ... ), + * "<ext>" => ( ... ) ) + */ + + for (j = 0; j < da->value->used; j++) { + size_t n; + data_array *da_ext = (data_array *)da->value->data[j]; + + if (da->value->data[j]->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sssbs", + "unexpected type for key: ", "fastcgi.server", + "[", da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + /* + * da_ext->key == name of the extension + */ + + /* + * fastcgi.server = ( "<ext>" => + * ( "<host>" => ( ... ), + * "<host>" => ( ... ) + * ), + * "<ext>" => ... ) + */ + + for (n = 0; n < da_ext->value->used; n++) { + data_array *da_host = (data_array *)da_ext->value->data[n]; + + fcgi_extension_host *df; + + config_values_t fcv[] = { + { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "docroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "mode", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */ + + { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */ + { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 6 */ + { "min-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 */ + { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */ + { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */ + { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */ + { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ + + { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ + { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ + + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (da_host->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "ssSBS", + "unexpected type for key:", + "fastcgi.server", + "[", da_host->key, "](string)"); + + return HANDLER_ERROR; + } + + df = fastcgi_host_init(); + + df->check_local = 1; + df->min_procs = 4; + df->max_procs = 4; + df->max_load_per_proc = 1; + df->idle_timeout = 60; + df->mode = FCGI_RESPONDER; + df->disable_time = 60; + + fcv[0].destination = df->host; + fcv[1].destination = df->docroot; + fcv[2].destination = fcgi_mode; + fcv[3].destination = df->unixsocket; + fcv[4].destination = df->bin_path; + + fcv[5].destination = &(df->check_local); + fcv[6].destination = &(df->port); + fcv[7].destination = &(df->min_procs); + fcv[8].destination = &(df->max_procs); + fcv[9].destination = &(df->max_load_per_proc); + fcv[10].destination = &(df->idle_timeout); + fcv[11].destination = &(df->disable_time); + + fcv[12].destination = df->bin_env; + fcv[13].destination = df->bin_env_copy; + + if (0 != config_insert_values_internal(srv, da_host->value, fcv)) { + return HANDLER_ERROR; + } + + if ((!buffer_is_empty(df->host) || df->port) && + !buffer_is_empty(df->unixsocket)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "either host+port or socket"); + + return HANDLER_ERROR; + } + + if (!buffer_is_empty(df->unixsocket)) { + /* unix domain socket */ + + if (df->unixsocket->used > UNIX_PATH_MAX - 2) { + log_error_write(srv, __FILE__, __LINE__, "s", + "path of the unixdomain socket is too large"); + return HANDLER_ERROR; + } + } else { + /* tcp/ip */ + + if (buffer_is_empty(df->host) && + buffer_is_empty(df->bin_path)) { + log_error_write(srv, __FILE__, __LINE__, "sbbbs", + "missing key (string):", + da->key, + da_ext->key, + da_host->key, + "host"); + + return HANDLER_ERROR; + } else if (df->port == 0) { + log_error_write(srv, __FILE__, __LINE__, "sbbbs", + "missing key (short):", + da->key, + da_ext->key, + da_host->key, + "port"); + return HANDLER_ERROR; + } + } + + if (!buffer_is_empty(df->bin_path)) { + /* a local socket + self spawning */ + size_t pno; + + if (df->min_procs > df->max_procs) df->max_procs = df->min_procs; + if (df->max_load_per_proc < 1) df->max_load_per_proc = 0; + + if (s->debug) { + log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd", + "--- fastcgi spawning local", + "\n\tproc:", df->bin_path, + "\n\tport:", df->port, + "\n\tsocket", df->unixsocket, + "\n\tmin-procs:", df->min_procs, + "\n\tmax-procs:", df->max_procs); + } + + for (pno = 0; pno < df->min_procs; pno++) { + fcgi_proc *proc; + + proc = fastcgi_process_init(); + proc->id = df->num_procs++; + df->max_id++; + + if (buffer_is_empty(df->unixsocket)) { + proc->port = df->port + pno; + } else { + buffer_copy_string_buffer(proc->socket, df->unixsocket); + buffer_append_string(proc->socket, "-"); + buffer_append_long(proc->socket, pno); + } + + if (s->debug) { + log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", + "--- fastcgi spawning", + "\n\tport:", df->port, + "\n\tsocket", df->unixsocket, + "\n\tcurrent:", pno, "/", df->min_procs); + } + + if (fcgi_spawn_connection(srv, p, df, proc)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "[ERROR]: spawning fcgi failed."); + return HANDLER_ERROR; + } + + proc->next = df->first; + if (df->first) df->first->prev = proc; + + df->first = proc; + } + } else { + fcgi_proc *fp; + + fp = fastcgi_process_init(); + fp->id = df->num_procs++; + df->max_id++; + df->active_procs++; + fp->state = PROC_STATE_RUNNING; + + if (buffer_is_empty(df->unixsocket)) { + fp->port = df->port; + } else { + buffer_copy_string_buffer(fp->socket, df->unixsocket); + } + + df->first = fp; + + df->min_procs = 1; + df->max_procs = 1; + } + + if (!buffer_is_empty(fcgi_mode)) { + if (strcmp(fcgi_mode->ptr, "responder") == 0) { + df->mode = FCGI_RESPONDER; + } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) { + df->mode = FCGI_AUTHORIZER; + if (buffer_is_empty(df->docroot)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: docroot is required for authorizer mode."); + return HANDLER_ERROR; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "WARNING: unknown fastcgi mode:", + fcgi_mode, "(ignored, mode set to responder)"); + } + } + + /* if extension already exists, take it */ + fastcgi_extension_insert(s->exts, da_ext->key, df); + } + } + } + } + + buffer_free(fcgi_mode); + + return HANDLER_GO_ON; +} + +static int fcgi_set_state(server *srv, handler_ctx *hctx, fcgi_connection_state_t state) { + hctx->state = state; + hctx->state_timestamp = srv->cur_ts; + + return 0; +} + + +static size_t fcgi_requestid_new(server *srv, plugin_data *p) { + size_t m = 0; + size_t i; + buffer_uint *r = &(p->fcgi_request_id); + + UNUSED(srv); + + for (i = 0; i < r->used; i++) { + if (r->ptr[i] > m) m = r->ptr[i]; + } + + if (r->size == 0) { + r->size = 16; + r->ptr = malloc(sizeof(*r->ptr) * r->size); + } else if (r->used == r->size) { + r->size += 16; + r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size); + } + + r->ptr[r->used++] = ++m; + + return m; +} + +static int fcgi_requestid_del(server *srv, plugin_data *p, size_t request_id) { + size_t i; + buffer_uint *r = &(p->fcgi_request_id); + + UNUSED(srv); + + for (i = 0; i < r->used; i++) { + if (r->ptr[i] == request_id) break; + } + + if (i != r->used) { + /* found */ + + if (i != r->used - 1) { + r->ptr[i] = r->ptr[r->used - 1]; + } + r->used--; + } + + return 0; +} + +void fcgi_connection_cleanup(server *srv, handler_ctx *hctx) { + plugin_data *p; + connection *con; + + if (NULL == hctx) return; + + p = hctx->plugin_data; + con = hctx->remote_conn; + + if (con->mode != p->id) { + WP(); + return; + } + + if (hctx->fd != -1) { + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + close(hctx->fd); + srv->cur_fds--; + } + + if (hctx->request_id != 0) { + fcgi_requestid_del(srv, p, hctx->request_id); + } + + if (hctx->host && hctx->proc) { + hctx->host->load--; + if (hctx->state != FCGI_STATE_INIT && + hctx->state != FCGI_STATE_CONNECT) { + /* after the connect the process gets a load */ + hctx->proc->load--; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sddb", + "release proc:", + hctx->fd, + hctx->proc->pid, hctx->proc->socket); + } + } + + fcgi_proclist_sort_down(srv, hctx->host, hctx->proc); + } + + + handler_ctx_free(hctx); + con->plugin_ctx[p->id] = NULL; +} + +static int fcgi_reconnect(server *srv, handler_ctx *hctx) { + plugin_data *p = hctx->plugin_data; + + /* child died + * + * 1. + * + * connect was ok, connection was accepted + * but the php accept loop checks after the accept if it should die or not. + * + * if yes we can only detect it at a write() + * + * next step is resetting this attemp and setup a connection again + * + * if we have more then 5 reconnects for the same request, die + * + * 2. + * + * we have a connection but the child died by some other reason + * + */ + + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + close(hctx->fd); + srv->cur_fds--; + + fcgi_requestid_del(srv, p, hctx->request_id); + + fcgi_set_state(srv, hctx, FCGI_STATE_INIT); + + hctx->request_id = 0; + hctx->reconnects++; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sddb", + "release proc:", + hctx->fd, + hctx->proc->pid, hctx->proc->socket); + } + + hctx->proc->load--; + fcgi_proclist_sort_down(srv, hctx->host, hctx->proc); + + return 0; +} + + +static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + fcgi_connection_cleanup(srv, con->plugin_ctx[p->id]); + + return HANDLER_GO_ON; +} + + +static int fcgi_env_add(buffer *env, const char *key, size_t key_len, const char *val, size_t val_len) { + size_t len; + + if (!key || !val) return -1; + + len = key_len + val_len; + + len += key_len > 127 ? 4 : 1; + len += val_len > 127 ? 4 : 1; + + buffer_prepare_append(env, len); + + if (key_len > 127) { + env->ptr[env->used++] = ((key_len >> 24) & 0xff) | 0x80; + env->ptr[env->used++] = (key_len >> 16) & 0xff; + env->ptr[env->used++] = (key_len >> 8) & 0xff; + env->ptr[env->used++] = (key_len >> 0) & 0xff; + } else { + env->ptr[env->used++] = (key_len >> 0) & 0xff; + } + + if (val_len > 127) { + env->ptr[env->used++] = ((val_len >> 24) & 0xff) | 0x80; + env->ptr[env->used++] = (val_len >> 16) & 0xff; + env->ptr[env->used++] = (val_len >> 8) & 0xff; + env->ptr[env->used++] = (val_len >> 0) & 0xff; + } else { + env->ptr[env->used++] = (val_len >> 0) & 0xff; + } + + memcpy(env->ptr + env->used, key, key_len); + env->used += key_len; + memcpy(env->ptr + env->used, val, val_len); + env->used += val_len; + + return 0; +} + +static int fcgi_header(FCGI_Header * header, unsigned char type, size_t request_id, int contentLength, unsigned char paddingLength) { + header->version = FCGI_VERSION_1; + header->type = type; + header->requestIdB0 = request_id & 0xff; + header->requestIdB1 = (request_id >> 8) & 0xff; + header->contentLengthB0 = contentLength & 0xff; + header->contentLengthB1 = (contentLength >> 8) & 0xff; + header->paddingLength = paddingLength; + header->reserved = 0; + + return 0; +} +/** + * + * returns + * -1 error + * 0 connected + * 1 not connected yet + */ + +static int fcgi_establish_connection(server *srv, handler_ctx *hctx) { + struct sockaddr *fcgi_addr; + struct sockaddr_in fcgi_addr_in; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un fcgi_addr_un; +#endif + socklen_t servlen; + + fcgi_extension_host *host = hctx->host; + fcgi_proc *proc = hctx->proc; + int fcgi_fd = hctx->fd; + + memset(&fcgi_addr, 0, sizeof(fcgi_addr)); + + if (!buffer_is_empty(proc->socket)) { +#ifdef HAVE_SYS_UN_H + /* use the unix domain socket */ + fcgi_addr_un.sun_family = AF_UNIX; + strcpy(fcgi_addr_un.sun_path, proc->socket->ptr); +#ifdef SUN_LEN + servlen = SUN_LEN(&fcgi_addr_un); +#else + /* stevens says: */ + servlen = proc->socket->used - 1 + sizeof(fcgi_addr_un.sun_family); +#endif + fcgi_addr = (struct sockaddr *) &fcgi_addr_un; +#else + return -1; +#endif + } else { + fcgi_addr_in.sin_family = AF_INET; + if (INADDR_NONE == (fcgi_addr_in.sin_addr.s_addr = inet_addr(host->host->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "converting IP-adress failed for", host->host, + "\nBe sure to specify an IP address here"); + + return -1; + } + fcgi_addr_in.sin_port = htons(proc->port); + servlen = sizeof(fcgi_addr_in); + + fcgi_addr = (struct sockaddr *) &fcgi_addr_in; + } + + if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) { + if (errno == EINPROGRESS || + errno == EALREADY || + errno == EINTR) { + if (hctx->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect delayed:", fcgi_fd); + } + + return 1; + } else { + log_error_write(srv, __FILE__, __LINE__, "sdsddb", + "connect failed:", fcgi_fd, + strerror(errno), errno, + proc->port, proc->socket); + + if (errno == EAGAIN) { + /* this is Linux only */ + + log_error_write(srv, __FILE__, __LINE__, "s", + "If this happend on Linux: You have been run out of local ports. " + "Check the manual, section Performance how to handle this."); + } + + return -1; + } + } + if (hctx->conf.debug > 1) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect succeeded: ", fcgi_fd); + } + + + + return 0; +} + +static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_data *p) { + size_t i; + + for (i = 0; i < con->request.headers->used; i++) { + data_string *ds; + + ds = (data_string *)con->request.headers->data[i]; + + if (ds->value->used && ds->key->used) { + size_t j; + buffer_reset(srv->tmp_buf); + + if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { + BUFFER_COPY_STRING_CONST(srv->tmp_buf, "HTTP_"); + srv->tmp_buf->used--; + } + + buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); + for (j = 0; j < ds->key->used - 1; j++) { + srv->tmp_buf->ptr[srv->tmp_buf->used++] = + light_isalpha(ds->key->ptr[j]) ? + ds->key->ptr[j] & ~32 : '_'; + } + srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; + + fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)); + } + } + + for (i = 0; i < con->environment->used; i++) { + data_string *ds; + + ds = (data_string *)con->environment->data[i]; + + if (ds->value->used && ds->key->used) { + size_t j; + buffer_reset(srv->tmp_buf); + + buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); + for (j = 0; j < ds->key->used - 1; j++) { + srv->tmp_buf->ptr[srv->tmp_buf->used++] = + isalpha((unsigned char)ds->key->ptr[j]) ? + toupper((unsigned char)ds->key->ptr[j]) : '_'; + } + srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; + + fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)); + } + } + + return 0; +} + + +static int fcgi_create_env(server *srv, handler_ctx *hctx, size_t request_id) { + FCGI_BeginRequestRecord beginRecord; + FCGI_Header header; + + char buf[32]; + size_t offset; + const char *s; +#ifdef HAVE_IPV6 + char b2[INET6_ADDRSTRLEN + 1]; +#endif + + plugin_data *p = hctx->plugin_data; + fcgi_extension_host *host= hctx->host; + + connection *con = hctx->remote_conn; + server_socket *srv_sock = con->srv_socket; + + /* send FCGI_BEGIN_REQUEST */ + + fcgi_header(&(beginRecord.header), FCGI_BEGIN_REQUEST, request_id, sizeof(beginRecord.body), 0); + beginRecord.body.roleB0 = host->mode; + beginRecord.body.roleB1 = 0; + beginRecord.body.flags = 0; + memset(beginRecord.body.reserved, 0, sizeof(beginRecord.body.reserved)); + + buffer_copy_memory(hctx->write_buffer, (const char *)&beginRecord, sizeof(beginRecord)); + + /* send FCGI_PARAMS */ + buffer_prepare_copy(p->fcgi_env, 1024); + + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION)); + + if (con->server_name->used) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name)); + } else { +#ifdef HAVE_IPV6 + s = inet_ntop(srv_sock->addr.plain.sa_family, + srv_sock->addr.plain.sa_family == AF_INET6 ? + (const void *) &(srv_sock->addr.ipv6.sin6_addr) : + (const void *) &(srv_sock->addr.ipv4.sin_addr), + b2, sizeof(b2)-1); +#else + s = inet_ntoa(srv_sock->addr.ipv4.sin_addr); +#endif + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s)); + } + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); + + ltostr(buf, +#ifdef HAVE_IPV6 + ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port) +#else + ntohs(srv_sock->addr.ipv4.sin_port) +#endif + ); + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf)); + + s = inet_ntop_cache_get_ip(srv, &(con->dst_addr)); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s)); + + if (!buffer_is_empty(con->authed_user)) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REMOTE_USER"), + CONST_BUF_LEN(con->authed_user)); + } + + if (con->request.content_length > 0 && host->mode != FCGI_AUTHORIZER) { + /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */ + + /* request.content_length < SSIZE_MAX, see request.c */ + ltostr(buf, con->request.content_length); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)); + } + + if (host->mode != FCGI_AUTHORIZER) { + /* + * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to + * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html + * (6.1.14, 6.1.6, 6.1.7) + * For AUTHORIZER mode these headers should be omitted. + */ + + if (hctx->path_info_offset == 0) { /* no pathinfo */ + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_STR_LEN("")); + } else { /* pathinfo */ + *(con->uri.path->ptr + hctx->path_info_offset) = '\0'; /* get sctipt_name part */ + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); + + *(con->uri.path->ptr + hctx->path_info_offset) = '/'; /* restore uri.path */ + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), + con->uri.path->ptr + hctx->path_info_offset, + con->uri.path->used - 1 - hctx->path_info_offset); + + if (host->docroot->used) { + buffer_copy_string_buffer(p->path, host->docroot); + buffer_append_string(p->path, con->uri.path->ptr + hctx->path_info_offset); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path)); + } else { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_TRANSLATED"), + con->uri.path->ptr + hctx->path_info_offset, + con->uri.path->used - 1 - hctx->path_info_offset); + } + } + } + + /* + * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual + * http://www.php.net/manual/en/reserved.variables.php + * treatment of PATH_TRANSLATED is different from the one of CGI specs. + * TODO: this code should be checked against cgi.fix_pathinfo php + * parameter. + */ + + if (!buffer_is_empty(host->docroot)) { + /* + * rewrite SCRIPT_FILENAME + * + */ + + buffer_copy_string_buffer(p->path, host->docroot); + buffer_append_string_buffer(p->path, con->uri.path); + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path)); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot)); + } else { + if (con->request.pathinfo->used) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); + } + + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); + } + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); + if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri)); + } + if (!buffer_is_empty(con->uri.query)) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)); + } else { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN("")); + } + + s = get_http_method_name(con->request.http_method); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */ + s = get_http_version_name(con->request.http_version); + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)); + +#ifdef USE_OPENSSL + if (srv_sock->is_ssl) { + fcgi_env_add(p->fcgi_env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on")); + } +#endif + + + fcgi_env_add_request_headers(srv, con, p); + + fcgi_header(&(header), FCGI_PARAMS, request_id, p->fcgi_env->used, 0); + buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header)); + buffer_append_memory(hctx->write_buffer, (const char *)p->fcgi_env->ptr, p->fcgi_env->used); + + fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0); + buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header)); + + /* send FCGI_STDIN */ + + /* something to send ? */ + for (offset = 0; offset != con->request.content_length; ) { + /* send chunks of 1024 bytes */ + size_t toWrite = con->request.content_length - offset > 4096 ? 4096 : con->request.content_length - offset; + + fcgi_header(&(header), FCGI_STDIN, request_id, toWrite, 0); + buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header)); + buffer_append_memory(hctx->write_buffer, (const char *)(con->request.content->ptr + offset), toWrite); + + offset += toWrite; + } + + /* terminate STDIN */ + fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); + buffer_append_memory(hctx->write_buffer, (const char *)&header, sizeof(header)); + +#if 0 + for (i = 0; i < hctx->write_buffer->used; i++) { + fprintf(stderr, "%02x ", hctx->write_buffer->ptr[i]); + if ((i+1) % 16 == 0) { + size_t j; + for (j = i-15; j <= i; j++) { + fprintf(stderr, "%c", + isprint((unsigned char)hctx->write_buffer->ptr[j]) ? hctx->write_buffer->ptr[j] : '.'); + } + fprintf(stderr, "\n"); + } + } +#endif + + return 0; +} + +static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) { + char *s, *ns; + + handler_ctx *hctx = con->plugin_ctx[p->id]; + fcgi_extension_host *host= hctx->host; + + UNUSED(srv); + + /* \r\n -> \0\0 */ + + buffer_copy_string_buffer(p->parse_response, in); + + for (s = p->parse_response->ptr; NULL != (ns = strstr(s, "\r\n")); s = ns + 2) { + char *key, *value; + int key_len; + data_string *ds; + + ns[0] = '\0'; + ns[1] = '\0'; + + key = s; + if (NULL == (value = strchr(s, ':'))) { + /* we expect: "<key>: <value>\n" */ + continue; + } + + key_len = value - key; + + value++; + /* strip WS */ + while (*value == ' ' || *value == '\t') value++; + + if (host->mode != FCGI_AUTHORIZER || + !(con->http_status == 0 || + con->http_status == 200)) { + /* authorizers shouldn't affect the response headers sent back to the client */ + if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { + ds = data_response_init(); + } + buffer_copy_string_len(ds->key, key, key_len); + buffer_copy_string(ds->value, value); + + array_insert_unique(con->response.headers, (data_unset *)ds); + } + + switch(key_len) { + case 4: + if (0 == strncasecmp(key, "Date", key_len)) { + con->parsed_response |= HTTP_DATE; + } + break; + case 6: + if (0 == strncasecmp(key, "Status", key_len)) { + con->http_status = strtol(value, NULL, 10); + con->parsed_response |= HTTP_STATUS; + } + break; + case 8: + if (0 == strncasecmp(key, "Location", key_len)) { + con->parsed_response |= HTTP_LOCATION; + } + break; + case 10: + if (0 == strncasecmp(key, "Connection", key_len)) { + con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0; + con->parsed_response |= HTTP_CONNECTION; + } + break; + case 14: + if (0 == strncasecmp(key, "Content-Length", key_len)) { + con->response.content_length = strtol(value, NULL, 10); + con->parsed_response |= HTTP_CONTENT_LENGTH; + + if (con->response.content_length < 0) con->response.content_length = 0; + } + break; + default: + break; + } + } + + /* CGI/1.1 rev 03 - 7.2.1.2 */ + if ((con->parsed_response & HTTP_LOCATION) && + !(con->parsed_response & HTTP_STATUS)) { + con->http_status = 302; + } + + return 0; +} + + +static int fcgi_demux_response(server *srv, handler_ctx *hctx) { + ssize_t len; + int fin = 0; + int b; + ssize_t r; + + plugin_data *p = hctx->plugin_data; + connection *con = hctx->remote_conn; + int fcgi_fd = hctx->fd; + fcgi_extension_host *host= hctx->host; + fcgi_proc *proc = hctx->proc; + + /* + * check how much we have to read + */ + if (ioctl(hctx->fd, FIONREAD, &b)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "unexpected end-of-file (perhaps the fastcgi process died):", + fcgi_fd); + return -1; + } + + /* init read-buffer */ + if (hctx->rb == NULL) { + hctx->rb = calloc(1, sizeof(*hctx->rb)); + } + + if (b > 0) { + if (hctx->rb->size == 0) { + hctx->rb->size = b; + hctx->rb->ptr = malloc(hctx->rb->size * sizeof(*hctx->rb->ptr)); + } else if (hctx->rb->size < hctx->rb->used + b) { + hctx->rb->size += b; + hctx->rb->ptr = realloc(hctx->rb->ptr, hctx->rb->size * sizeof(*hctx->rb->ptr)); + } + + /* append to read-buffer */ + if (-1 == (r = read(hctx->fd, hctx->rb->ptr + hctx->rb->used, b))) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "unexpected end-of-file (perhaps the fastcgi process died):", + fcgi_fd, strerror(errno)); + return -1; + } + + /* this should be catched by the b > 0 above */ + assert(r); + + hctx->rb->used += r; + } else { + log_error_write(srv, __FILE__, __LINE__, "ssdsdsd", + "unexpected end-of-file (perhaps the fastcgi process died):", + "pid:", proc->pid, + "fcgi-fd:", fcgi_fd, + "remote-fd:", con->fd); + + return -1; + } + + /* parse all fcgi packets + * + * start: hctx->rb->ptr + * end : hctx->rb->ptr + hctx->rb->used + * + */ + while (fin == 0) { + size_t request_id; + + if (hctx->response_len == 0) { + FCGI_Header *header; + + if (hctx->rb->used - hctx->rb->offset < sizeof(*header)) { + /* didn't get the full header packet (most often 0), + * but didn't recieved the final packet either + * + * we will come back later and finish everything + * + */ + + hctx->delayed = 1; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sddd", "didn't get the full header: ", + hctx->rb->used - hctx->rb->offset, sizeof(*header), + fcgi_fd + ); +#endif + break; + } +#if 0 + fprintf(stderr, "fcgi-version: %02x\n", hctx->rb->ptr[hctx->rb->offset]); +#endif + + header = (FCGI_Header *)(hctx->rb->ptr + hctx->rb->offset); + hctx->rb->offset += sizeof(*header); + + len = (header->contentLengthB0 | (header->contentLengthB1 << 8)) + header->paddingLength; + request_id = (header->requestIdB0 | (header->requestIdB1 << 8)); + + hctx->response_len = len; + hctx->response_request_id = request_id; + hctx->response_type = header->type; + hctx->response_padding = header->paddingLength; + +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sddd", "offset: ", + fcgi_fd, hctx->rb->offset, header->type + ); +#endif + + } else { + len = hctx->response_len; + } + + if (hctx->rb->used - hctx->rb->offset < hctx->response_len) { + /* we are not finished yet */ + break; + } + + hctx->response->ptr = hctx->rb->ptr + hctx->rb->offset; + hctx->rb->offset += hctx->response_len; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdd", "offset: ", + fcgi_fd, hctx->rb->offset + ); +#endif + + /* remove padding */ +#if 0 + hctx->response->ptr[hctx->response_len - hctx->response_padding] = '\0'; +#endif + hctx->response->used = hctx->response_len - hctx->response_padding + 1; + + /* mark the fast-cgi packet as finished */ + hctx->response_len = 0; + + switch(hctx->response_type) { + case FCGI_STDOUT: + if (len) { +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdb", "len", len, hctx->response); +#endif + + if (0 == con->got_response) { + con->got_response = 1; + buffer_prepare_copy(hctx->response_header, 128); + } + + if (0 == con->file_started) { + char *c; + + /* search for the \r\n\r\n in the string */ + if (NULL != (c = buffer_search_string_len(hctx->response, "\r\n\r\n", 4))) { + size_t hlen = c - hctx->response->ptr + 4; + size_t blen = hctx->response->used - hlen - 1; + /* found */ + + buffer_append_string_len(hctx->response_header, hctx->response->ptr, c - hctx->response->ptr + 4); +#if 0 + log_error_write(srv, __FILE__, __LINE__, "ss", "Header:", hctx->response_header->ptr); +#endif + /* parse the response header */ + fcgi_response_parse(srv, con, p, hctx->response_header); + + + if (host->mode != FCGI_AUTHORIZER || + !(con->http_status == 0 || + con->http_status == 200)) { + /* enable chunked-transfer-encoding */ + if (con->request.http_version == HTTP_VERSION_1_1 && + !(con->parsed_response & HTTP_CONTENT_LENGTH)) { + con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + } + + con->file_started = 1; + + if (blen) { + http_chunk_append_mem(srv, con, c + 4, blen + 1); + joblist_append(srv, con); +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "body-len", blen); +#endif + } + } + } else { + /* copy everything */ + buffer_append_string_buffer(hctx->response_header, hctx->response); + } + } else { + if (host->mode != FCGI_AUTHORIZER || + !(con->http_status == 0 || + con->http_status == 200)) { + http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); + joblist_append(srv, con); + } +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "body-len", hctx->response->used); +#endif + } + } else { + /* finished */ + } + + break; + case FCGI_STDERR: + log_error_write(srv, __FILE__, __LINE__, "sb", + "FastCGI-stderr:", hctx->response); + + break; + case FCGI_END_REQUEST: + con->file_finished = 1; + + if (host->mode != FCGI_AUTHORIZER || + !(con->http_status == 0 || + con->http_status == 200)) { + /* send chunk-end if nesseary */ + http_chunk_append_mem(srv, con, NULL, 0); + joblist_append(srv, con); + } + + fin = 1; + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", + "FastCGI: header.type not handled: ", hctx->response_type); + break; + } + } + + hctx->response->ptr = NULL; + + return fin; +} + +int fcgi_proclist_sort_up(server *srv, fcgi_extension_host *host, fcgi_proc *proc) { + fcgi_proc *p; + + UNUSED(srv); + + /* we have been the smallest of the current list + * and we want to insert the node sorted as soon + * possible + * + * 1 0 0 0 1 1 1 + * | ^ + * | | + * +------+ + * + */ + + /* nothing to sort, only one element */ + if (host->first == proc && proc->next == NULL) return 0; + + for (p = proc; p->next && p->next->load < proc->load; p = p->next); + + /* no need to move something + * + * 1 2 2 2 3 3 3 + * ^ + * | + * + + * + */ + if (p == proc) return 0; + + if (host->first == proc) { + /* we have been the first elememt */ + + host->first = proc->next; + host->first->prev = NULL; + } + + /* disconnect proc */ + + if (proc->prev) proc->prev->next = proc->next; + if (proc->next) proc->next->prev = proc->prev; + + /* proc should be right of p */ + + proc->next = p->next; + proc->prev = p; + if (p->next) p->next->prev = proc; + p->next = proc; +#if 0 + for(p = host->first; p; p = p->next) { + log_error_write(srv, __FILE__, __LINE__, "dd", + p->pid, p->load); + } +#else + UNUSED(srv); +#endif + + return 0; +} + +int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc) { + fcgi_proc *p; + + UNUSED(srv); + + /* we have been the smallest of the current list + * and we want to insert the node sorted as soon + * possible + * + * 0 0 0 0 1 0 1 + * ^ | + * | | + * +----------+ + * + * + * the basic is idea is: + * - the last active fastcgi process should be still + * in ram and is not swapped out yet + * - processes that are not reused will be killed + * after some time by the trigger-handler + * - remember it as: + * everything > 0 is hot + * all unused procs are colder the more right they are + * ice-cold processes are propably unused since more + * than 'unused-timeout', are swaped out and won't be + * reused in the next seconds anyway. + * + */ + + /* nothing to sort, only one element */ + if (host->first == proc && proc->next == NULL) return 0; + + for (p = host->first; p != proc && p->load < proc->load; p = p->next); + + + /* no need to move something + * + * 1 2 2 2 3 3 3 + * ^ + * | + * + + * + */ + if (p == proc) return 0; + + /* we have to move left. If we are already the first element + * we are done */ + if (host->first == proc) return 0; + + /* release proc */ + if (proc->prev) proc->prev->next = proc->next; + if (proc->next) proc->next->prev = proc->prev; + + /* proc should be left of p */ + proc->next = p; + proc->prev = p->prev; + if (p->prev) p->prev->next = proc; + p->prev = proc; + + if (proc->prev == NULL) host->first = proc; +#if 0 + for(p = host->first; p; p = p->next) { + log_error_write(srv, __FILE__, __LINE__, "dd", + p->pid, p->load); + } +#else + UNUSED(srv); +#endif + + return 0; +} + + + +static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { + plugin_data *p = hctx->plugin_data; + fcgi_extension_host *host= hctx->host; + connection *con = hctx->remote_conn; + + int r; + + /* sanity check */ + if (!host || + ((!host->host->used || !host->port) && !host->unixsocket->used)) return HANDLER_ERROR; + + switch(hctx->state) { + case FCGI_STATE_INIT: + r = host->unixsocket->used ? AF_UNIX : AF_INET; + + if (-1 == (hctx->fd = socket(r, SOCK_STREAM, 0))) { + if (errno == EMFILE || + errno == EINTR) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "wait for fd at connection:", con->fd); + + return HANDLER_WAIT_FOR_FD; + } + + log_error_write(srv, __FILE__, __LINE__, "ssdd", + "socket failed:", strerror(errno), srv->cur_fds, srv->max_fds); + return HANDLER_ERROR; + } + hctx->fde_ndx = -1; + + srv->cur_fds++; + + fdevent_register(srv->ev, hctx->fd, fcgi_handle_fdevent, hctx); + + if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "fcntl failed: ", strerror(errno)); + + return HANDLER_ERROR; + } + + /* fall through */ + case FCGI_STATE_CONNECT: + if (hctx->state == FCGI_STATE_INIT) { + for (hctx->proc = hctx->host->first; + hctx->proc && hctx->proc->state != PROC_STATE_RUNNING; + hctx->proc = hctx->proc->next); + + /* all childs are dead */ + if (hctx->proc == NULL) { + hctx->fde_ndx = -1; + + return HANDLER_ERROR; + } + + + switch (fcgi_establish_connection(srv, hctx)) { + case 1: + fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT); + + /* connection is in progress, wait for an event and call getsockopt() below */ + + return HANDLER_WAIT_FOR_EVENT; + case -1: + /* if ECONNREFUSED choose another connection -> FIXME */ + hctx->fde_ndx = -1; + + return HANDLER_ERROR; + default: + /* everything is ok, go on */ + break; + } + + + } else { + int socket_error; + socklen_t socket_error_len = sizeof(socket_error); + + /* try to finish the connect() */ + if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "getsockopt failed:", strerror(errno)); + + return HANDLER_ERROR; + } + if (socket_error != 0) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "establishing connection failed:", strerror(socket_error), + "port:", hctx->proc->port); + + return HANDLER_ERROR; + } + } + + /* ok, we have the connection */ + + hctx->proc->load++; + hctx->proc->last_used = srv->cur_ts; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sddbdd", + "got proc:", + hctx->fd, + hctx->proc->pid, + hctx->proc->socket, + hctx->proc->port, + hctx->proc->load); + } + + /* move the proc-list entry down the list */ + fcgi_proclist_sort_up(srv, hctx->host, hctx->proc); + + if (hctx->request_id == 0) { + hctx->request_id = fcgi_requestid_new(srv, p); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "fcgi-request is already in use:", hctx->request_id); + } + + fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE); + /* fall through */ + case FCGI_STATE_PREPARE_WRITE: + fcgi_create_env(srv, hctx, hctx->request_id); + + fcgi_set_state(srv, hctx, FCGI_STATE_WRITE); + hctx->write_offset = 0; + + /* fall through */ + case FCGI_STATE_WRITE: + /* continue with the code after the switch */ + if (-1 == (r = write(hctx->fd, + hctx->write_buffer->ptr + hctx->write_offset, + hctx->write_buffer->used - hctx->write_offset))) { + + if (errno == ENOTCONN) { + /* the connection got dropped after accept() + * + * this is most of the time a PHP which dies + * after PHP_FCGI_MAX_REQUESTS + * + */ + if (hctx->write_offset == 0 && + hctx->reconnects < 5) { + usleep(10000); /* take away the load of the webserver + * to let the php a chance to restart + */ + + fcgi_reconnect(srv, hctx); + + return HANDLER_WAIT_FOR_FD; + } + + /* not reconnected ... why + * + * far@#lighttpd report this for FreeBSD + * + */ + + log_error_write(srv, __FILE__, __LINE__, "ssdsd", + "[REPORT ME] connection was dropped after accept(). reconnect() denied:", + "write-offset:", hctx->write_offset, + "reconnect attempts:", hctx->reconnects); + + + + return HANDLER_ERROR; + } + + if ((errno != EAGAIN) && + (errno != EINTR)) { + + log_error_write(srv, __FILE__, __LINE__, "ssd", + "write failed:", strerror(errno), errno); + + return HANDLER_ERROR; + } else { + return HANDLER_WAIT_FOR_EVENT; + } + } + + hctx->write_offset += r; + + if (hctx->write_offset == hctx->write_buffer->used) { + fcgi_set_state(srv, hctx, FCGI_STATE_READ); + } + + break; + case FCGI_STATE_READ: + /* waiting for a response */ + break; + default: + log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state"); + return HANDLER_ERROR; + } + + return HANDLER_WAIT_FOR_EVENT; +} + +static int fcgi_restart_dead_procs(server *srv, plugin_data *p, fcgi_extension_host *host) { + fcgi_proc *proc; + + for (proc = host->first; proc; proc = proc->next) { + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sbdbdddd", + "proc:", + host->host, proc->port, + proc->socket, + proc->state, + proc->is_local, + proc->load, + proc->pid); + } + + if (0 == proc->is_local) { + /* + * external servers might get disabled + * + * enable the server again, perhaps it is back again + */ + + if ((proc->state == PROC_STATE_DISABLED) && + (srv->cur_ts - proc->disable_ts > FCGI_RETRY_TIMEOUT)) { + proc->state = PROC_STATE_RUNNING; + host->active_procs++; + + log_error_write(srv, __FILE__, __LINE__, "sbdb", + "fcgi-server re-enabled:", + host->host, host->port, + host->unixsocket); + } + } else { + /* the child should not terminate at all */ + int status; + + if (proc->state == PROC_STATE_DIED_WAIT_FOR_PID) { + switch(waitpid(proc->pid, &status, WNOHANG)) { + case 0: + /* child is still alive */ + break; + case -1: + break; + default: + if (WIFEXITED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sdsd", + "child exited, pid:", proc->pid, + "status:", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child signaled:", + WTERMSIG(status)); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child died somehow:", + status); + } + + proc->state = PROC_STATE_DIED; + break; + } + } + + /* + * local servers might died, but we restart them + * + */ + if (proc->state == PROC_STATE_DIED && + proc->load == 0) { + /* restart the child */ + + if (fcgi_spawn_connection(srv, p, host, proc)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: spawning fcgi failed."); + return HANDLER_ERROR; + } + + fcgi_proclist_sort_down(srv, host, proc); + } + } + } + + return 0; +} + +SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { + plugin_data *p = p_d; + + handler_ctx *hctx = con->plugin_ctx[p->id]; + fcgi_proc *proc; + fcgi_extension_host *host; + + if (NULL == hctx) return HANDLER_GO_ON; + + /* not my job */ + if (con->mode != p->id) return HANDLER_GO_ON; + + /* ok, create the request */ + switch(fcgi_write_request(srv, hctx)) { + case HANDLER_ERROR: + proc = hctx->proc; + host = hctx->host; + + if (proc && + 0 == proc->is_local && + proc->state != PROC_STATE_DISABLED) { + /* only disable remote servers as we don't manage them*/ + + log_error_write(srv, __FILE__, __LINE__, "sbdb", "fcgi-server disabled:", + host->host, + proc->port, + proc->socket); + + /* disable this server */ + proc->disable_ts = srv->cur_ts; + proc->state = PROC_STATE_DISABLED; + host->active_procs--; + } + + if (hctx->state == FCGI_STATE_INIT || + hctx->state == FCGI_STATE_CONNECT) { + /* connect() or getsockopt() failed, + * restart the request-handling + */ + if (proc && proc->is_local) { + log_error_write(srv, __FILE__, __LINE__, "sbdb", "connect() to fastcgi failed, restarting the request-handling:", + host->host, + proc->port, + proc->socket); + + proc->state = PROC_STATE_DIED_WAIT_FOR_PID; + } + + fcgi_restart_dead_procs(srv, p, host); + + fcgi_connection_cleanup(srv, hctx); + + buffer_reset(con->physical.path); + con->mode = DIRECT; + + joblist_append(srv, con); + + /* mis-using HANDLER_WAIT_FOR_FD to break out of the loop + * and hope that the childs will be restarted + * + */ + return HANDLER_WAIT_FOR_FD; + } else { + + fcgi_connection_cleanup(srv, hctx); + + buffer_reset(con->physical.path); + con->mode = DIRECT; + con->http_status = 503; + + return HANDLER_FINISHED; + } + case HANDLER_WAIT_FOR_EVENT: + if (con->file_started == 1) { + return HANDLER_FINISHED; + } else { + return HANDLER_WAIT_FOR_EVENT; + } + case HANDLER_WAIT_FOR_FD: + return HANDLER_WAIT_FOR_FD; + default: + return HANDLER_ERROR; + } +} + +static handler_t fcgi_connection_close(server *srv, handler_ctx *hctx) { + plugin_data *p; + connection *con; + + if (NULL == hctx) return HANDLER_GO_ON; + + p = hctx->plugin_data; + con = hctx->remote_conn; + + if (con->mode != p->id) return HANDLER_GO_ON; + + log_error_write(srv, __FILE__, __LINE__, "ssdsd", + "emergency exit: fastcgi:", + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + + + fcgi_connection_cleanup(srv, hctx); + + return HANDLER_FINISHED; +} + + +static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) { + server *srv = (server *)s; + handler_ctx *hctx = ctx; + connection *con = hctx->remote_conn; + plugin_data *p = hctx->plugin_data; + + fcgi_proc *proc = hctx->proc; + fcgi_extension_host *host= hctx->host; + + joblist_append(srv, con); + + if ((revents & FDEVENT_IN) && + hctx->state == FCGI_STATE_READ) { + switch (fcgi_demux_response(srv, hctx)) { + case 0: + break; + case 1: + + if (host->mode == FCGI_AUTHORIZER && + (con->http_status == 200 || + con->http_status == 0)) { + /* + * If we are here in AUTHORIZER mode then a request for autorizer + * was proceeded already, and status 200 has been returned. We need + * now to handle autorized request. + */ + + buffer_copy_string_buffer(con->physical.doc_root, host->docroot); + + buffer_copy_string_buffer(con->physical.path, host->docroot); + buffer_append_string_buffer(con->physical.path, con->uri.path); + fcgi_connection_cleanup(srv, hctx); + + con->mode = DIRECT; + con->file_started = 1; /* fcgi_extension won't touch the request afterwards */ + } else { + /* we are done */ + fcgi_connection_cleanup(srv, hctx); + } + + return HANDLER_FINISHED; + case -1: + if (proc->pid) { + int status; + switch(waitpid(proc->pid, &status, WNOHANG)) { + case 0: + /* child is still alive */ + break; + case -1: + break; + default: + /* the child should not terminate at all */ + if (WIFEXITED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sdsd", + "child exited, pid:", proc->pid, + "status:", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child signaled:", + WTERMSIG(status)); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child died somehow:", + status); + } + + if (fcgi_spawn_connection(srv, p, host, proc)) { + /* child died */ + proc->state = PROC_STATE_DIED; + } else { + fcgi_proclist_sort_down(srv, host, proc); + } + + break; + } + } + + if (con->file_started == 0) { + /* nothing has been send out yet, try to use another child */ + + if (hctx->write_offset == 0 && + hctx->reconnects < 5) { + fcgi_reconnect(srv, hctx); + + log_error_write(srv, __FILE__, __LINE__, "sdsdsd", + "response not sent, request not sent, reconnection.", + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + return HANDLER_WAIT_FOR_FD; + } + + log_error_write(srv, __FILE__, __LINE__, "sdsdsd", + "response not sent, request sent:", hctx->write_offset, + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + fcgi_connection_cleanup(srv, hctx); + + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + buffer_reset(con->physical.path); + con->http_status = 500; + con->mode = DIRECT; + } else { + /* response might have been already started, kill the connection */ + fcgi_connection_cleanup(srv, hctx); + + log_error_write(srv, __FILE__, __LINE__, "ssdsd", + "response already sent out, termination connection", + "connection-fd:", con->fd, + "fcgi-fd:", hctx->fd); + + connection_set_state(srv, con, CON_STATE_ERROR); + } + + /* */ + + + return HANDLER_FINISHED; + } + } + + if (revents & FDEVENT_OUT) { + if (hctx->state == FCGI_STATE_CONNECT || + hctx->state == FCGI_STATE_WRITE) { + /* we are allowed to send something out + * + * 1. in a unfinished connect() call + * 2. in a unfinished write() call (long POST request) + */ + return mod_fastcgi_handle_subrequest(srv, con, p); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "got a FDEVENT_OUT and didn't know why:", + hctx->state); + } + } + + /* perhaps this issue is already handled */ + if (revents & FDEVENT_HUP) { + if (hctx->state == FCGI_STATE_CONNECT) { + /* getoptsock will catch this one (right ?) + * + * if we are in connect we might get a EINPROGRESS + * in the first call and a FDEVENT_HUP in the + * second round + * + * FIXME: as it is a bit ugly. + * + */ + return mod_fastcgi_handle_subrequest(srv, con, p); + } else if (hctx->state == FCGI_STATE_READ && + hctx->proc->port == 0) { + /* FIXME: + * + * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket + * even if the FCGI_FIN packet is not received yet + */ + } else { + log_error_write(srv, __FILE__, __LINE__, "sbSBSDSd", + "error: unexpected close of fastcgi connection for", + con->uri.path, + "(no fastcgi process on host: ", + host->host, + ", port: ", + host->port, + " ?)", + hctx->state); + + connection_set_state(srv, con, CON_STATE_ERROR); + fcgi_connection_close(srv, hctx); + } + } else if (revents & FDEVENT_ERR) { + log_error_write(srv, __FILE__, __LINE__, "s", + "fcgi: got a FDEVENT_ERR. Don't know why."); + /* kill all connections to the fastcgi process */ + + + connection_set_state(srv, con, CON_STATE_ERROR); + fcgi_connection_close(srv, hctx); + } + + return HANDLER_FINISHED; +} +#define PATCH(x) \ + p->conf.x = s->x; +static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.server"))) { + PATCH(exts); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("fastcgi.debug"))) { + PATCH(debug); + } + } + } + + return 0; +} + +static int fcgi_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(exts); + PATCH(debug); + + return 0; +} +#undef PATCH + + +static handler_t fcgi_check_extension(server *srv, connection *con, void *p_d, int uri_path_handler) { + plugin_data *p = p_d; + size_t s_len; + int used = -1; + int ndx; + size_t k, i; + buffer *fn; + fcgi_extension *extension = NULL; + size_t path_info_offset; + + /* Possibly, we processed already this request */ + if (con->file_started == 1) return HANDLER_GO_ON; + + fn = uri_path_handler ? con->uri.path : con->physical.path; + + if (fn->used == 0) { + return HANDLER_ERROR; + } + + s_len = fn->used - 1; + + /* select the right config */ + fcgi_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + fcgi_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + path_info_offset = 0; + + /* check if extension matches */ + for (k = 0; k < p->conf.exts->used; k++) { + size_t ct_len; + + extension = p->conf.exts->exts[k]; + + if (extension->key->used == 0) continue; + + ct_len = extension->key->used - 1; + + if (s_len < ct_len) continue; + + /* check extension in the form "/fcgi_pattern" */ + if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) { + if (s_len > ct_len + 1) { + char *pi_offset; + + if (0 != (pi_offset = strchr(fn->ptr + ct_len + 1, '/'))) { + path_info_offset = pi_offset - fn->ptr; + } + } + break; + } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) { + /* check extension in the form ".fcg" */ + break; + } + } + + /* extension doesn't match */ + if (k == p->conf.exts->used) { + return HANDLER_GO_ON; + } + + /* get best server */ + for (k = 0, ndx = -1; k < extension->used; k++) { + fcgi_extension_host *host = extension->hosts[k]; + + /* we should have at least one proc that can do somthing */ + if (host->active_procs == 0) continue; + + if (used == -1 || host->load < used) { + used = host->load; + + ndx = k; + } + } + + /* found a server */ + if (ndx != -1) { + fcgi_extension_host *host = extension->hosts[ndx]; + + /* + * if check-local is disabled, use the uri.path handler + * + */ + + /* init handler-context */ + if (uri_path_handler) { + if (host->check_local == 0) { + handler_ctx *hctx; + hctx = handler_ctx_init(); + + hctx->path_info_offset = path_info_offset; + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->host = host; + hctx->proc = NULL; + + hctx->conf.exts = p->conf.exts; + hctx->conf.debug = p->conf.debug; + + con->plugin_ctx[p->id] = hctx; + + host->load++; + + con->mode = p->id; + } + return HANDLER_GO_ON; + } else { + handler_ctx *hctx; + hctx = handler_ctx_init(); + + hctx->path_info_offset = path_info_offset; + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->host = host; + hctx->proc = NULL; + + hctx->conf.exts = p->conf.exts; + hctx->conf.debug = p->conf.debug; + + con->plugin_ctx[p->id] = hctx; + + host->load++; + + con->mode = p->id; + + return HANDLER_FINISHED; + } + } else { + /* no handler found */ + buffer_reset(con->physical.path); + con->http_status = 500; + + log_error_write(srv, __FILE__, __LINE__, "sb", + "no fcgi-handler found for:", + fn); + + return HANDLER_FINISHED; + } + return HANDLER_GO_ON; +} + +/* uri-path handler */ +static handler_t fcgi_check_extension_1(server *srv, connection *con, void *p_d) { + return fcgi_check_extension(srv, con, p_d, 1); +} + +/* start request handler */ +static handler_t fcgi_check_extension_2(server *srv, connection *con, void *p_d) { + return fcgi_check_extension(srv, con, p_d, 0); +} + +JOBLIST_FUNC(mod_fastcgi_handle_joblist) { + plugin_data *p = p_d; + handler_ctx *hctx = con->plugin_ctx[p->id]; + + if (hctx == NULL) return HANDLER_GO_ON; + + if (hctx->fd != -1) { + switch (hctx->state) { + case FCGI_STATE_READ: + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + + break; + case FCGI_STATE_CONNECT: + case FCGI_STATE_WRITE: + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + break; + case FCGI_STATE_INIT: + /* at reconnect */ + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled fcgi.state", hctx->state); + break; + } + } + + return HANDLER_GO_ON; +} + + +static handler_t fcgi_connection_close_callback(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + return fcgi_connection_close(srv, con->plugin_ctx[p->id]); +} + +TRIGGER_FUNC(mod_fastcgi_handle_trigger) { + plugin_data *p = p_d; + size_t i, j, n; + + + /* perhaps we should kill a connect attempt after 10-15 seconds + * + * currently we wait for the TCP timeout which is on Linux 180 seconds + * + * + * + */ + + /* check all childs if they are still up */ + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *conf; + fcgi_exts *exts; + + conf = p->config_storage[i]; + + exts = conf->exts; + + for (j = 0; j < exts->used; j++) { + fcgi_extension *ex; + + ex = exts->exts[j]; + + for (n = 0; n < ex->used; n++) { + + fcgi_proc *proc; + unsigned long sum_load = 0; + fcgi_extension_host *host; + + host = ex->hosts[n]; + + fcgi_restart_dead_procs(srv, p, host); + + for (proc = host->first; proc; proc = proc->next) { + sum_load += proc->load; + } + + if (host->num_procs && + host->num_procs < host->max_procs && + (sum_load / host->num_procs) > host->max_load_per_proc) { + /* overload, spawn new child */ + fcgi_proc *fp = NULL; + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "s", + "overload detected, spawning a new child"); + } + + for (fp = host->unused_procs; fp && fp->pid != 0; fp = fp->next); + + if (fp) { + if (fp == host->unused_procs) host->unused_procs = fp->next; + + if (fp->next) fp->next->prev = NULL; + + host->max_id++; + } else { + fp = fastcgi_process_init(); + fp->id = host->max_id++; + } + + host->num_procs++; + + if (buffer_is_empty(host->unixsocket)) { + fp->port = host->port + fp->id; + } else { + buffer_copy_string_buffer(fp->socket, host->unixsocket); + buffer_append_string(fp->socket, "-"); + buffer_append_long(fp->socket, fp->id); + } + + if (fcgi_spawn_connection(srv, p, host, fp)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "ERROR: spawning fcgi failed."); + return HANDLER_ERROR; + } + + fp->prev = NULL; + fp->next = host->first; + if (host->first) { + host->first->prev = fp; + } + host->first = fp; + } + + for (proc = host->first; proc; proc = proc->next) { + if (proc->load != 0) break; + if (host->num_procs <= host->min_procs) break; + if (proc->pid == 0) continue; + + if (srv->cur_ts - proc->last_used > host->idle_timeout) { + /* a proc is idling for a long time now, + * terminated it */ + + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ssbsd", + "idle-timeout reached, terminating child:", + "socket:", proc->socket, + "pid", proc->pid); + } + + + if (proc->next) proc->next->prev = proc->prev; + if (proc->prev) proc->prev->next = proc->next; + + if (proc->prev == NULL) host->first = proc->next; + + proc->prev = NULL; + proc->next = host->unused_procs; + + if (host->unused_procs) host->unused_procs->prev = proc; + host->unused_procs = proc; + + kill(proc->pid, SIGTERM); + + proc->state = PROC_STATE_KILLED; + + log_error_write(srv, __FILE__, __LINE__, "ssbsd", + "killed:", + "socket:", proc->socket, + "pid", proc->pid); + + host->num_procs--; + + /* proc is now in unused, let the next second handle the next process */ + break; + } + } + + for (proc = host->unused_procs; proc; proc = proc->next) { + int status; + + if (proc->pid == 0) continue; + + switch (waitpid(proc->pid, &status, WNOHANG)) { + case 0: + /* child still running after timeout, good */ + break; + case -1: + if (errno != EINTR) { + /* no PID found ? should never happen */ + log_error_write(srv, __FILE__, __LINE__, "sddss", + "pid ", proc->pid, proc->state, + "not found:", strerror(errno)); + +#if 0 + if (errno == ECHILD) { + /* someone else has cleaned up for us */ + proc->pid = 0; + proc->state = PROC_STATE_UNSET; + } +#endif + } + break; + default: + /* the child should not terminate at all */ + if (WIFEXITED(status)) { + if (proc->state != PROC_STATE_KILLED) { + log_error_write(srv, __FILE__, __LINE__, "sdb", + "child exited:", + WEXITSTATUS(status), proc->socket); + } + } else if (WIFSIGNALED(status)) { + if (WTERMSIG(status) != SIGTERM) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child signaled:", + WTERMSIG(status)); + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", + "child died somehow:", + status); + } + proc->pid = 0; + proc->state = PROC_STATE_UNSET; + host->max_id--; + } + } + } + } + } + + return HANDLER_GO_ON; +} + + +int mod_fastcgi_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("fastcgi"); + + p->init = mod_fastcgi_init; + p->cleanup = mod_fastcgi_free; + p->set_defaults = mod_fastcgi_set_defaults; + p->connection_reset = fcgi_connection_reset; + p->handle_connection_close = fcgi_connection_close_callback; + p->handle_uri_clean = fcgi_check_extension_1; + p->handle_subrequest_start = fcgi_check_extension_2; + p->handle_subrequest = mod_fastcgi_handle_subrequest; + p->handle_joblist = mod_fastcgi_handle_joblist; + p->handle_trigger = mod_fastcgi_handle_trigger; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_mysql_vhost.c b/src/mod_mysql_vhost.c new file mode 100644 index 00000000..76ff96aa --- /dev/null +++ b/src/mod_mysql_vhost.c @@ -0,0 +1,425 @@ +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <strings.h> + +#include "plugin.h" +#include "config.h" +#include "log.h" + +#include "file_cache.h" +#ifdef HAVE_MYSQL +#include <mysql/mysql.h> +#endif +#ifdef DEBUG_MOD_MYSQL_VHOST +#define DEBUG +#endif + +/* + * Plugin for lighttpd to use MySQL + * for domain to directory lookups, + * i.e virtual hosts (vhosts). + * + * Optionally sets fcgi_offset and fcgi_arg + * in preparation for fcgi.c to handle + * per-user fcgi chroot jails. + * + * /ada@riksnet.se 2004-12-06 + */ + +typedef struct { +#ifdef HAVE_MYSQL + MYSQL *mysql; +#endif + buffer *mydb; + buffer *myuser; + buffer *mypass; + buffer *mysock; + + buffer *mysql_pre; + buffer *mysql_post; +} plugin_config; + +/* global plugin data */ +typedef struct { + PLUGIN_DATA; + + buffer *tmp_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* per connection plugin data */ +typedef struct { + buffer *server_name; + buffer *document_root; + buffer *fcgi_arg; + unsigned fcgi_offset; +} plugin_connection_data; + +/* init the plugin data */ +INIT_FUNC(mod_mysql_vhost_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->tmp_buf = buffer_init(); + + return p; +} + +/* cleanup the plugin data */ +SERVER_FUNC(mod_mysql_vhost_cleanup) { + plugin_data *p = p_d; + + UNUSED(srv); + +#ifdef DEBUG + log_error_write(srv, __FILE__, __LINE__, "ss", + "mod_mysql_vhost_cleanup", p ? "yes" : "NO"); +#endif + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; +#ifdef HAVE_MYSQL + mysql_close(s->mysql); +#endif + buffer_free(s->mydb); + buffer_free(s->myuser); + buffer_free(s->mypass); + buffer_free(s->mysock); + buffer_free(s->mysql_pre); + buffer_free(s->mysql_post); + + free(s); + } + free(p->config_storage); + } + buffer_free(p->tmp_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle the plugin per connection data */ +static void* mod_mysql_vhost_connection_data(server *srv, connection *con, void *p_d) +{ + plugin_data *p = p_d; + plugin_connection_data *c = con->plugin_ctx[p->id]; + + UNUSED(srv); + +#ifdef DEBUG + log_error_write(srv, __FILE__, __LINE__, "ss", + "mod_mysql_connection_data", c ? "old" : "NEW"); +#endif + + if (c) return c; + c = calloc(1, sizeof(*c)); + + c->server_name = buffer_init(); + c->document_root = buffer_init(); + c->fcgi_arg = buffer_init(); + c->fcgi_offset = 0; + + return con->plugin_ctx[p->id] = c; +} + +/* destroy the plugin per connection data */ +CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close) { + plugin_data *p = p_d; + plugin_connection_data *c = con->plugin_ctx[p->id]; + + UNUSED(srv); + +#ifdef DEBUG + log_error_write(srv, __FILE__, __LINE__, "ss", + "mod_mysql_vhost_handle_connection_close", c ? "yes" : "NO"); +#endif + + if (!c) return HANDLER_GO_ON; + + buffer_free(c->server_name); + buffer_free(c->document_root); + buffer_free(c->fcgi_arg); + c->fcgi_offset = 0; + + free(c); + + con->plugin_ctx[p->id] = NULL; + return HANDLER_GO_ON; +} + +/* set configuration values */ +SERVER_FUNC(mod_mysql_vhost_set_defaults) { + plugin_data *p = p_d; + + char *qmark; + size_t i = 0; + + config_values_t cv[] = { + { "mysql-vhost.db", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, + { "mysql-vhost.user", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, + { "mysql-vhost.pass", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, + { "mysql-vhost.sock", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, + { "mysql-vhost.sql", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + buffer *sel; + + + s = malloc(sizeof(plugin_config)); + s->mydb = buffer_init(); + s->myuser = buffer_init(); + s->mypass = buffer_init(); + s->mysock = buffer_init(); + sel = buffer_init(); +#ifdef HAVE_MYSQL + s->mysql = NULL; +#endif + + s->mysql_pre = buffer_init(); + s->mysql_post = buffer_init(); + + cv[0].destination = s->mydb; + cv[1].destination = s->myuser; + cv[2].destination = s->mypass; + cv[3].destination = s->mysock; + cv[4].destination = sel; + + p->config_storage[i] = s; + + if (config_insert_values_global(srv, + ((data_config *)srv->config_context->data[i])->value, + cv)) return HANDLER_ERROR; + + s->mysql_pre = buffer_init(); + s->mysql_post = buffer_init(); + + if (sel->used && (qmark = index(sel->ptr, '?'))) { + *qmark = '\0'; + buffer_copy_string(s->mysql_pre, sel->ptr); + buffer_copy_string(s->mysql_post, qmark+1); + } else { + buffer_copy_string_buffer(s->mysql_pre, sel); + } + + /* all have to be set */ + if (!(buffer_is_empty(s->myuser) || + buffer_is_empty(s->mypass) || + buffer_is_empty(s->mydb) || + buffer_is_empty(s->mysock))) { +#ifdef HAVE_MYSQL + int fd; + + if (NULL == (s->mysql = mysql_init(NULL))) { + log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting..."); + + return HANDLER_ERROR; + } + + if (!mysql_real_connect(s->mysql, NULL, s->myuser->ptr, s->mypass->ptr, + s->mydb->ptr, 0, s->mysock->ptr, 0)) { + log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql)); + + return HANDLER_ERROR; + } + + /* set close_on_exec for mysql the hard way */ + /* Note: this only works as it is done during startup, */ + /* otherwise we cannot be sure that mysql is fd i-1 */ + if (-1 == (fd = open("/dev/null", 0))) { + close(fd); + fcntl(fd-1, F_SETFD, FD_CLOEXEC); + } +#endif + } + } + + + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_mysql_vhost_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("mysql-vhost.sql"))) { + PATCH(mysql_pre); + PATCH(mysql_post); + } + } + +#ifdef HAVE_MYSQL + if (s->mysql) { + PATCH(mysql); + } +#endif + } + + return 0; +} + +static int mod_mysql_vhost_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + + UNUSED(srv); + UNUSED(con); + + PATCH(mysql_pre); + PATCH(mysql_post); +#ifdef HAVE_MYSQL + PATCH(mysql); +#endif + + return 0; +} +#undef PATCH + + +/* handle document root request */ +CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) { +#ifdef HAVE_MYSQL + plugin_data *p = p_d; + plugin_connection_data *c; + + unsigned cols; + MYSQL_ROW row; + MYSQL_RES *result = NULL; + size_t i; + + /* no host specified? */ + if (!con->uri.authority->used) return HANDLER_GO_ON; + + /* apply conditionals */ + mod_mysql_vhost_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_mysql_vhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + /* sets up connection data if not done yet */ + c = mod_mysql_vhost_connection_data(srv, con, p_d); + + /* check if cached this connection */ + if (c->server_name->used && /* con->uri.authority->used && */ + buffer_is_equal(c->server_name, con->uri.authority)) goto GO_ON; + + /* build and run SQL query */ + buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre); + if (p->conf.mysql_post->used) { + buffer_append_string_buffer(p->tmp_buf, con->uri.authority); + buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post); + } + if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) { + log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql)); + goto ERR500; + } + result = mysql_store_result(p->conf.mysql); + cols = mysql_num_fields(result); + row = mysql_fetch_row(result); + if (!row || cols < 1) { + /* no such virtual host */ + mysql_free_result(result); + return HANDLER_GO_ON; + } + + /* sanity check that really is a directory */ + buffer_copy_string(p->tmp_buf, row[0]); + BUFFER_APPEND_SLASH(p->tmp_buf); + if (file_cache_get_entry(srv, con, p->tmp_buf, &(con->fce)) != HANDLER_GO_ON) { + log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf); + goto ERR500; + } + if (!S_ISDIR(con->fce->st.st_mode)) { + log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", p->tmp_buf); + goto ERR500; + } + + /* cache the data */ + buffer_copy_string_buffer(c->server_name, con->uri.authority); + buffer_copy_string_buffer(c->document_root, p->tmp_buf); + + /* fcgi_offset and fcgi_arg are optional */ + if (cols > 1 && row[1]) { + c->fcgi_offset = atoi(row[1]); + + if (cols > 2 && row[2]) { + buffer_copy_string(c->fcgi_arg, row[2]); + } else { + c->fcgi_arg->used = 0; + } + } else { + c->fcgi_offset = c->fcgi_arg->used = 0; + } + mysql_free_result(result); + + /* fix virtual server and docroot */ +GO_ON: buffer_copy_string_buffer(con->server_name, c->server_name); + buffer_copy_string_buffer(con->physical.doc_root, c->document_root); + +#ifdef DEBUG + log_error_write(srv, __FILE__, __LINE__, "sbbdb", + result ? "NOT CACHED" : "cached", + con->server_name, con->physical.doc_root, + c->fcgi_offset, c->fcgi_arg); +#endif + return HANDLER_GO_ON; + +ERR500: if (result) mysql_free_result(result); + con->http_status = 500; /* Internal Error */ + return HANDLER_FINISHED; +#else + UNUSED(srv); + UNUSED(con); + UNUSED(p_d); + + return HANDLER_ERROR; +#endif +} + +/* this function is called at dlopen() time and inits the callbacks */ +int mod_mysql_vhost_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("mysql_vhost"); + + p->init = mod_mysql_vhost_init; + p->cleanup = mod_mysql_vhost_cleanup; + p->handle_connection_close = mod_mysql_vhost_handle_connection_close; + + p->set_defaults = mod_mysql_vhost_set_defaults; + p->handle_docroot = mod_mysql_vhost_handle_docroot; + + return 0; +} + diff --git a/src/mod_proxy.c b/src/mod_proxy.c new file mode 100644 index 00000000..48bab9a4 --- /dev/null +++ b/src/mod_proxy.c @@ -0,0 +1,1093 @@ +#include <sys/types.h> + +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <assert.h> + +#include "buffer.h" +#include "server.h" +#include "keyvalue.h" +#include "log.h" + +#include "http_chunk.h" +#include "fdevent.h" +#include "connections.h" +#include "response.h" +#include "joblist.h" + +#include "plugin.h" + +#include "inet_ntop_cache.h" + +#include <stdio.h> + +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#include "sys-socket.h" + +#define data_proxy data_fastcgi +#define data_proxy_init data_fastcgi_init + +#define PROXY_RETRY_TIMEOUT 60 + +/** + * + * the proxy module is based on the fastcgi module + * + * 28.06.2004 Jan Kneschke The first release + * 01.07.2004 Evgeny Rodichev Several bugfixes and cleanups + * - co-ordinate up- and downstream flows correctly (proxy_demux_response + * and proxy_handle_fdevent) + * - correctly transfer upstream http_response_status; + * - some unused structures removed. + * + * TODO: - delay upstream read if write_queue is too large + * (to prevent memory eating, like in apache). Shoud be + * configurable). + * - persistent connection with upstream servers + * - HTTP/1.1 + */ + +typedef struct { + array *extensions; + int debug; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *parse_response; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +typedef enum { PROXY_STATE_INIT, PROXY_STATE_CONNECT, PROXY_STATE_PREPARE_WRITE, PROXY_STATE_WRITE, PROXY_STATE_READ, PROXY_STATE_ERROR } proxy_connection_state_t; +enum { PROXY_STDOUT, PROXY_END_REQUEST }; +typedef struct { + proxy_connection_state_t state; + time_t state_timestamp; + + data_proxy *host; + + buffer *response; + buffer *response_header; + + buffer *write_buffer; + size_t write_offset; + + + int fd; /* fd to the proxy process */ + int fde_ndx; /* index into the fd-event buffer */ + + size_t path_info_offset; /* start of path_info in uri.path */ + + connection *remote_conn; /* dump pointer */ + plugin_data *plugin_data; /* dump pointer */ +} handler_ctx; + + +/* ok, we need a prototype */ +static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents); + +static handler_ctx * handler_ctx_init() { + handler_ctx * hctx; + + + hctx = calloc(1, sizeof(*hctx)); + + hctx->state = PROXY_STATE_INIT; + hctx->host = NULL; + + hctx->response = buffer_init(); + hctx->response_header = buffer_init(); + + hctx->write_buffer = buffer_init(); + + hctx->fd = -1; + hctx->fde_ndx = -1; + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + buffer_free(hctx->response); + buffer_free(hctx->response_header); + buffer_free(hctx->write_buffer); + + free(hctx); +} + +INIT_FUNC(mod_proxy_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->parse_response = buffer_init(); + + return p; +} + + +FREE_FUNC(mod_proxy_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + buffer_free(p->parse_response); + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if (s) { + + array_free(s->extensions); + + free(s); + } + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_proxy_set_defaults) { + plugin_data *p = p_d; + data_unset *du; + size_t i = 0; + + config_values_t cv[] = { + { "proxy.server", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "proxy.debug", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + array *ca; + + s = malloc(sizeof(plugin_config)); + s->extensions = array_init(); + s->debug = 0; + + cv[0].destination = s->extensions; + cv[1].destination = &(s->debug); + + p->config_storage[i] = s; + ca = ((data_config *)srv->config_context->data[i])->value; + + if (0 != config_insert_values_global(srv, ca, cv)) { + return HANDLER_ERROR; + } + + if (NULL != (du = array_get_element(ca, "proxy.server"))) { + size_t j; + data_array *da = (data_array *)du; + + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", "proxy.server", "array of strings"); + + return HANDLER_ERROR; + } + + /* + * proxy.server = ( "<ext>" => ..., + * "<ext>" => ... ) + */ + + for (j = 0; j < da->value->used; j++) { + data_array *da_ext = (data_array *)da->value->data[j]; + size_t n; + + if (da_ext->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sssbs", + "unexpected type for key: ", "proxy.server", + "[", da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + /* + * proxy.server = ( "<ext>" => + * ( "<host>" => ( ... ), + * "<host>" => ( ... ) + * ), + * "<ext>" => ... ) + */ + + for (n = 0; n < da_ext->value->used; n++) { + data_array *da_host = (data_array *)da_ext->value->data[n]; + + data_proxy *df; + data_array *dfa; + + config_values_t pcv[] = { + { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (da_host->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "ssSBS", + "unexpected type for key:", + "proxy.server", + "[", da_ext->value->data[n]->key, "](string)"); + + return HANDLER_ERROR; + } + + df = data_proxy_init(); + + buffer_copy_string_buffer(df->key, da_host->key); + + pcv[0].destination = df->host; + pcv[1].destination = &(df->port); + + if (0 != config_insert_values_internal(srv, da_host->value, pcv)) { + return HANDLER_ERROR; + } + + if (buffer_is_empty(df->host)) { + log_error_write(srv, __FILE__, __LINE__, "sbbbs", + "missing key (string):", + da->key, + da_ext->key, + da_host->key, + "host"); + + return HANDLER_ERROR; + } else if (df->port == 0) { + log_error_write(srv, __FILE__, __LINE__, "sbbbs", + "missing key (short):", + da->key, + da_ext->key, + da_host->key, + "port"); + return HANDLER_ERROR; + } + + /* if extension already exists, take it */ + + if (NULL == (dfa = (data_array *)array_get_element(s->extensions, da_ext->key->ptr))) { + dfa = data_array_init(); + + buffer_copy_string_buffer(dfa->key, da_ext->key); + + array_insert_unique(dfa->value, (data_unset *)df); + array_insert_unique(s->extensions, (data_unset *)dfa); + } else { + array_insert_unique(dfa->value, (data_unset *)df); + } + } + } + } + } + + return HANDLER_GO_ON; +} + +void proxy_connection_cleanup(server *srv, handler_ctx *hctx) { + plugin_data *p; + connection *con; + + if (NULL == hctx) return; + + p = hctx->plugin_data; + con = hctx->remote_conn; + + if (con->mode != p->id) return; + + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + if (hctx->fd != -1) { + close(hctx->fd); + srv->cur_fds--; + } + + handler_ctx_free(hctx); + con->plugin_ctx[p->id] = NULL; +} + +static handler_t mod_proxy_connection_reset(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + proxy_connection_cleanup(srv, con->plugin_ctx[p->id]); + + return HANDLER_GO_ON; +} + +static int proxy_establish_connection(server *srv, handler_ctx *hctx) { + struct sockaddr *proxy_addr; + struct sockaddr_in proxy_addr_in; + socklen_t servlen; + + plugin_data *p = hctx->plugin_data; + data_proxy *host= hctx->host; + int proxy_fd = hctx->fd; + + memset(&proxy_addr, 0, sizeof(proxy_addr)); + + proxy_addr_in.sin_family = AF_INET; + proxy_addr_in.sin_addr.s_addr = inet_addr(host->host->ptr); + proxy_addr_in.sin_port = htons(host->port); + servlen = sizeof(proxy_addr_in); + + proxy_addr = (struct sockaddr *) &proxy_addr_in; + + if (-1 == connect(proxy_fd, proxy_addr, servlen)) { + if (errno == EINPROGRESS || errno == EALREADY) { + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect delayed:", proxy_fd); + } + + return -proxy_fd; + } else { + + log_error_write(srv, __FILE__, __LINE__, "sdsd", + "connect failed:", proxy_fd, strerror(errno), errno); + + proxy_connection_cleanup(srv, hctx); + + return -1; + } + } + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "connect succeeded: ", proxy_fd); + } + return proxy_fd; +} + +static int proxy_create_env(server *srv, handler_ctx *hctx) { + size_t i; + + connection *con = hctx->remote_conn; + UNUSED(srv); + + /* build header */ + + buffer_reset(hctx->write_buffer); + + /* request line */ + switch(con->request.http_method) { + case HTTP_METHOD_GET: + BUFFER_COPY_STRING_CONST(hctx->write_buffer, "GET "); + break; + case HTTP_METHOD_POST: + BUFFER_COPY_STRING_CONST(hctx->write_buffer, "POST "); + break; + case HTTP_METHOD_HEAD: + BUFFER_COPY_STRING_CONST(hctx->write_buffer, "HEAD "); + break; + default: + return -1; + } + + buffer_append_string_buffer(hctx->write_buffer, con->request.uri); + BUFFER_APPEND_STRING_CONST(hctx->write_buffer, " HTTP/1.0\r\n"); + + /* request header */ + for (i = 0; i < con->request.headers->used; i++) { + data_string *ds; + + ds = (data_string *)con->request.headers->data[i]; + + if (ds->value->used && ds->key->used) { + if (0 == strcmp(ds->key->ptr, "Connection")) continue; + + buffer_append_string_buffer(hctx->write_buffer, ds->key); + BUFFER_APPEND_STRING_CONST(hctx->write_buffer, ": "); + buffer_append_string_buffer(hctx->write_buffer, ds->value); + BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n"); + } + } + + BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "X-Forwarded-For: "); + buffer_append_string(hctx->write_buffer, inet_ntop_cache_get_ip(srv, &(con->dst_addr))); + BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n"); + + BUFFER_APPEND_STRING_CONST(hctx->write_buffer, "\r\n"); + + /* body */ + + if (con->request.http_method == HTTP_METHOD_POST && + con->request.content_length) { + /* the buffer-string functions add an extra \0 at the end the memory-function don't */ + hctx->write_buffer->used--; + buffer_append_memory(hctx->write_buffer, con->request.content->ptr, con->request.content_length); + } + + return 0; +} + +static int proxy_set_state(server *srv, handler_ctx *hctx, proxy_connection_state_t state) { + hctx->state = state; + hctx->state_timestamp = srv->cur_ts; + + return 0; +} + + +static int proxy_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) { + char *s, *ns; + int http_response_status = -1; + + UNUSED(srv); + + /* \r\n -> \0\0 */ + + buffer_copy_string_buffer(p->parse_response, in); + + for (s = p->parse_response->ptr; NULL != (ns = strstr(s, "\r\n")); s = ns + 2) { + char *key, *value; + int key_len; + data_string *ds; + + ns[0] = '\0'; + ns[1] = '\0'; + + if (-1 == http_response_status) { + /* The first line of a Response message is the Status-Line */ + + for (key=s; *key && *key != ' '; key++); + + if (*key) { + http_response_status = (int) strtol(key, NULL, 10); + if (http_response_status <= 0) http_response_status = 502; + } else { + http_response_status = 502; + } + + con->http_status = http_response_status; + con->parsed_response |= HTTP_STATUS; + continue; + } + + if (NULL == (value = strchr(s, ':'))) { + /* now we expect: "<key>: <value>\n" */ + + continue; + } + + key = s; + key_len = value - key; + + value++; + /* strip WS */ + while (*value == ' ' || *value == '\t') value++; + + + if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { + ds = data_response_init(); + } + buffer_copy_string_len(ds->key, key, key_len); + buffer_copy_string(ds->value, value); + + array_insert_unique(con->response.headers, (data_unset *)ds); + + switch(key_len) { + case 4: + if (0 == strncasecmp(key, "Date", key_len)) { + con->parsed_response |= HTTP_DATE; + } + break; + case 8: + if (0 == strncasecmp(key, "Location", key_len)) { + con->parsed_response |= HTTP_LOCATION; + } + break; + case 10: + if (0 == strncasecmp(key, "Connection", key_len)) { + con->response.keep_alive = (0 == strcasecmp(value, "Keep-Alive")) ? 1 : 0; + con->parsed_response |= HTTP_CONNECTION; + } + break; + case 14: + if (0 == strncasecmp(key, "Content-Length", key_len)) { + con->response.content_length = strtol(value, NULL, 10); + con->parsed_response |= HTTP_CONTENT_LENGTH; + } + break; + default: + break; + } + } + + return 0; +} + + +static int proxy_demux_response(server *srv, handler_ctx *hctx) { + int fin = 0; + int b; + ssize_t r; + + plugin_data *p = hctx->plugin_data; + connection *con = hctx->remote_conn; + int proxy_fd = hctx->fd; + + /* check how much we have to read */ + if (ioctl(hctx->fd, FIONREAD, &b)) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "ioctl failed: ", + proxy_fd); + return -1; + } + + if (b > 0) { + if (hctx->response->used == 0) { + /* avoid too small buffer */ + buffer_prepare_append(hctx->response, b + 1); + hctx->response->used = 1; + } else { + buffer_prepare_append(hctx->response, hctx->response->used + b); + } + + if (-1 == (r = read(hctx->fd, hctx->response->ptr + hctx->response->used - 1, b))) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "unexpected end-of-file (perhaps the proxy process died):", + proxy_fd, strerror(errno)); + return -1; + } + + /* this should be catched by the b > 0 above */ + assert(r); + + hctx->response->used += r; + hctx->response->ptr[hctx->response->used - 1] = '\0'; + +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdsbs", + "demux: Response buffer len", hctx->response->used, ":", hctx->response, ":"); +#endif + + if (0 == con->got_response) { + con->got_response = 1; + buffer_prepare_copy(hctx->response_header, 128); + } + + if (0 == con->file_started) { + char *c; + + /* search for the \r\n\r\n in the string */ + if (NULL != (c = buffer_search_string_len(hctx->response, "\r\n\r\n", 4))) { + size_t hlen = c - hctx->response->ptr + 4; + size_t blen = hctx->response->used - hlen - 1; + /* found */ + + buffer_append_string_len(hctx->response_header, hctx->response->ptr, c - hctx->response->ptr + 4); +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sb", "Header:", hctx->response_header); +#endif + /* parse the response header */ + proxy_response_parse(srv, con, p, hctx->response_header); + + /* enable chunked-transfer-encoding */ + if (con->request.http_version == HTTP_VERSION_1_1 && + !(con->parsed_response & HTTP_CONTENT_LENGTH)) { + con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + } + + con->file_started = 1; + if (blen) { + http_chunk_append_mem(srv, con, c + 4, blen + 1); + joblist_append(srv, con); + } + hctx->response->used = 0; + } + } else { + http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); + joblist_append(srv, con); + hctx->response->used = 0; + } + + } else { + /* reading from upstream done */ + con->file_finished = 1; + + http_chunk_append_mem(srv, con, NULL, 0); + joblist_append(srv, con); + + fin = 1; + } + + return fin; +} + + +static int proxy_write_request(server *srv, handler_ctx *hctx) { + data_proxy *host= hctx->host; + + int r; + + if (!host || + (!host->host->used || !host->port)) return -1; + + switch(hctx->state) { + case PROXY_STATE_INIT: + r = AF_INET; + + if (-1 == (hctx->fd = socket(r, SOCK_STREAM, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed: ", strerror(errno)); + return -1; + } + hctx->fde_ndx = -1; + + srv->cur_fds++; + + fdevent_register(srv->ev, hctx->fd, proxy_handle_fdevent, hctx); + + if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); + + proxy_connection_cleanup(srv, hctx); + + return -1; + } + + proxy_set_state(srv, hctx, PROXY_STATE_CONNECT); + /* fall through */ + + case PROXY_STATE_CONNECT: + /* try to finish the connect() */ + hctx->fd = proxy_establish_connection(srv, hctx); + + if (hctx->fd == -1) { + hctx->fde_ndx = -1; + return -1; + } + + if (hctx->fd < 0) { + hctx->fd = -hctx->fd; + proxy_set_state(srv, hctx, PROXY_STATE_CONNECT); + return 0; + } + + proxy_set_state(srv, hctx, PROXY_STATE_PREPARE_WRITE); + /* fall through */ + case PROXY_STATE_PREPARE_WRITE: + proxy_create_env(srv, hctx); + + proxy_set_state(srv, hctx, PROXY_STATE_WRITE); + hctx->write_offset = 0; + + /* fall through */ + case PROXY_STATE_WRITE: + /* continue with the code after the switch */ + if (-1 == (r = write(hctx->fd, + hctx->write_buffer->ptr + hctx->write_offset, + hctx->write_buffer->used - hctx->write_offset))) { + if (errno != EAGAIN) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), r); + + return -1; + } else { + return 0; + } + } + + hctx->write_offset += r; + + if (hctx->write_offset == hctx->write_buffer->used) { + proxy_set_state(srv, hctx, PROXY_STATE_READ); + } + + break; + case PROXY_STATE_READ: + /* waiting for a response */ + break; + default: + log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state"); + return -1; + } + + return 0; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_proxy_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.server"))) { + PATCH(extensions); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("proxy.debug"))) { + PATCH(debug); + } + } + } + + return 0; +} + +static int mod_proxy_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(extensions); + PATCH(debug); + + return 0; +} +#undef PATCH + + +SUBREQUEST_FUNC(mod_proxy_handle_subrequest) { + plugin_data *p = p_d; + + handler_ctx *hctx = con->plugin_ctx[p->id]; + data_proxy *host; + size_t i; + + if (NULL == hctx) return HANDLER_GO_ON; + + /* select the right config */ + mod_proxy_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_proxy_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + host = hctx->host; + + /* not my job */ + if (con->mode != p->id) return HANDLER_GO_ON; + + /* ok, create the request */ + if (-1 == proxy_write_request(srv, hctx)) { + log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:", + host->host, + host->port, + hctx->fd); + + /* disable this server */ + host->usage = -1; + host->disable_ts = srv->cur_ts; + + con->mode = DIRECT; + con->http_status = 503; + return HANDLER_FINISHED; + } + + if (con->file_started == 1) { + return HANDLER_FINISHED; + } else { + return HANDLER_WAIT_FOR_EVENT; + } +} + +static handler_t proxy_connection_close(server *srv, handler_ctx *hctx) { + plugin_data *p; + connection *con; + + if (NULL == hctx) return HANDLER_GO_ON; + + p = hctx->plugin_data; + con = hctx->remote_conn; + + if (con->mode != p->id) return HANDLER_GO_ON; + + log_error_write(srv, __FILE__, __LINE__, "ssdsd", + "emergency exit: proxy:", + "connection-fd:", con->fd, + "proxy-fd:", hctx->fd); + + + + proxy_connection_cleanup(srv, hctx); + + return HANDLER_FINISHED; +} + + +static handler_t proxy_handle_fdevent(void *s, void *ctx, int revents) { + server *srv = (server *)s; + handler_ctx *hctx = ctx; + connection *con = hctx->remote_conn; + plugin_data *p = hctx->plugin_data; + + joblist_append(srv, con); + + if ((revents & FDEVENT_IN) && + hctx->state == PROXY_STATE_READ) { + switch (proxy_demux_response(srv, hctx)) { + case 0: + break; + case 1: + hctx->host->usage--; + + /* we are done */ + + if (chunkqueue_is_empty(con->write_queue)) { + connection_set_state(srv, con, CON_STATE_RESPONSE_END); + } + + proxy_connection_cleanup(srv, hctx); + + return HANDLER_FINISHED; + case -1: + if (con->file_started == 0) { + /* nothing has been send out yet, send a 500 */ + connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); + con->http_status = 500; + con->mode = DIRECT; + } else { + /* response might have been already started, kill the connection */ + connection_set_state(srv, con, CON_STATE_ERROR); + } + + return HANDLER_FINISHED; + } + } + + if (revents & FDEVENT_OUT) { + if (hctx->state == PROXY_STATE_CONNECT || + hctx->state == PROXY_STATE_WRITE) { + /* we are allowed to send something out + * + * 1. in a unfinished connect() call + * 2. in a unfinished write() call (long POST request) + */ + return mod_proxy_handle_subrequest(srv, con, p); + } else { + log_error_write(srv, __FILE__, __LINE__, "sd", "proxy: out", hctx->state); + } + } + + /* perhaps this issue is already handled */ + if (revents & FDEVENT_HUP) { + log_error_write(srv, __FILE__, __LINE__, "sbSBSDS", + "error: unexpected close of proxy connection for", + con->uri.path, + "(no proxy process on host: ", + hctx->host->host, + ", port: ", + hctx->host->port, + " ?)" ); + +#ifndef USE_LINUX_SIGIO + proxy_connection_close(srv, hctx); +# if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", "proxy-FDEVENT_HUP", con->fd); +# endif + return HANDLER_ERROR; +#endif + } else if (revents & FDEVENT_ERR) { + log_error_write(srv, __FILE__, __LINE__, "s", "proxy: err"); + /* kill all connections to the proxy process */ + + proxy_connection_close(srv, hctx); +#if 1 + log_error_write(srv, __FILE__, __LINE__, "s", "proxy-FDEVENT_ERR"); +#endif + return HANDLER_ERROR; + } + + return HANDLER_FINISHED; +} + +static handler_t mod_proxy_check_extension(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + size_t s_len; + int used = -1; + int ndx; + size_t k, i; + buffer *fn; + data_array *extension = NULL; + size_t path_info_offset; + + /* Possibly, we processed already this request */ + if (con->file_started == 1) return HANDLER_GO_ON; + + /* select the right config */ + mod_proxy_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_proxy_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + fn = con->uri.path; + + if (fn->used == 0) { + return HANDLER_ERROR; + } + + s_len = fn->used - 1; + + + path_info_offset = 0; + + /* check if extension matches */ + for (k = 0; k < p->conf.extensions->used; k++) { + size_t ct_len; + + extension = (data_array *)p->conf.extensions->data[k]; + + if (extension->key->used == 0) continue; + + ct_len = extension->key->used - 1; + + if (s_len < ct_len) continue; + + /* check extension in the form "/proxy_pattern" */ + if (*(extension->key->ptr) == '/' && strncmp(fn->ptr, extension->key->ptr, ct_len) == 0) { + if (s_len > ct_len + 1) { + char *pi_offset; + + if (0 != (pi_offset = strchr(fn->ptr + ct_len + 1, '/'))) { + path_info_offset = pi_offset - fn->ptr; + } + } + break; + } else if (0 == strncmp(fn->ptr + s_len - ct_len, extension->key->ptr, ct_len)) { + /* check extension in the form ".fcg" */ + break; + } + } + + /* extension doesn't match */ + if (k == p->conf.extensions->used) { + return HANDLER_GO_ON; + } + + /* get best server */ + for (k = 0, ndx = -1; k < extension->value->used; k++) { + data_proxy *host = (data_proxy *)extension->value->data[k]; + + /* enable the server again, perhaps it is back again */ + if ((host->usage == -1) && + (srv->cur_ts - host->disable_ts > PROXY_RETRY_TIMEOUT)) { + host->usage = 0; + + log_error_write(srv, __FILE__, __LINE__, "sbd", "proxy-server re-enabled:", + host->host, host->port); + } + + if (used == -1 || host->usage < used) { + used = host->usage; + + ndx = k; + } + } + + /* found a server */ + if (ndx != -1) { + data_proxy *host = (data_proxy *)extension->value->data[ndx]; + + /* + * if check-local is disabled, use the uri.path handler + * + */ + + /* init handler-context */ + handler_ctx *hctx; + hctx = handler_ctx_init(); + + hctx->path_info_offset = path_info_offset; + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->host = host; + + con->plugin_ctx[p->id] = hctx; + + host->usage++; + + con->mode = p->id; + + return HANDLER_GO_ON; + } else { + /* no handler found */ + con->http_status = 500; + + log_error_write(srv, __FILE__, __LINE__, "sb", + "no proxy-handler found for:", + fn); + + return HANDLER_FINISHED; + } + return HANDLER_GO_ON; +} + +JOBLIST_FUNC(mod_proxy_handle_joblist) { + plugin_data *p = p_d; + handler_ctx *hctx = con->plugin_ctx[p->id]; + + if (hctx == NULL) return HANDLER_GO_ON; + + if (hctx->fd != -1) { + switch (hctx->state) { + case PROXY_STATE_READ: + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + + break; + case PROXY_STATE_CONNECT: + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled proxy.state", hctx->state); + break; + } + } + + return HANDLER_GO_ON; +} + + +static handler_t mod_proxy_connection_close_callback(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + + return proxy_connection_close(srv, con->plugin_ctx[p->id]); +} + +int mod_proxy_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("proxy"); + + p->init = mod_proxy_init; + p->cleanup = mod_proxy_free; + p->set_defaults = mod_proxy_set_defaults; + p->connection_reset = mod_proxy_connection_reset; + p->handle_connection_close = mod_proxy_connection_close_callback; + p->handle_uri_clean = mod_proxy_check_extension; + p->handle_subrequest = mod_proxy_handle_subrequest; + p->handle_joblist = mod_proxy_handle_joblist; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_redirect.c b/src/mod_redirect.c new file mode 100644 index 00000000..5c5ecf25 --- /dev/null +++ b/src/mod_redirect.c @@ -0,0 +1,271 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" +#include "response.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +typedef struct { + pcre_keyvalue_buffer *redirect; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + buffer *match_buf; + buffer *location; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_redirect_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->match_buf = buffer_init(); + p->location = buffer_init(); + + return p; +} + +FREE_FUNC(mod_redirect_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + buffer_free(p->match_buf); + buffer_free(p->location); + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_redirect_set_defaults) { + plugin_data *p = p_d; + data_unset *du; + size_t i = 0; + + config_values_t cv[] = { + { "url.redirect", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + /* 0 */ + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + size_t j; + array *ca; + data_array *da = (data_array *)du; + + s = malloc(sizeof(plugin_config)); + s->redirect = pcre_keyvalue_buffer_init(); + + cv[0].destination = s->redirect; + + p->config_storage[i] = s; + ca = ((data_config *)srv->config_context->data[i])->value; + + if (0 != config_insert_values_global(srv, ca, cv)) { + return HANDLER_ERROR; + } + + if (NULL == (du = array_get_element(ca, "url.redirect"))) { + /* no url.redirect defined */ + continue; + } + + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", "url.redirect", "array of strings"); + + return HANDLER_ERROR; + } + + da = (data_array *)du; + + for (j = 0; j < da->value->used; j++) { + if (da->value->data[j]->type != TYPE_STRING) { + log_error_write(srv, __FILE__, __LINE__, "sssbs", + "unexpected type for key: ", + "url.redirect", + "[", da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + if (0 != pcre_keyvalue_buffer_append(s->redirect, + ((data_string *)(da->value->data[j]))->key->ptr, + ((data_string *)(da->value->data[j]))->value->ptr)) { + + log_error_write(srv, __FILE__, __LINE__, "sb", + "pcre-compile failed for", da->value->data[j]->key); + } + } + } + + return HANDLER_GO_ON; +} +#ifdef HAVE_PCRE_H +static int mod_redirect_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (0 == strcmp(du->key->ptr, "url.redirect")) { + p->conf.redirect = s->redirect; + } + } + } + + return 0; +} + +static int mod_redirect_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + + UNUSED(srv); + UNUSED(con); + + p->conf.redirect = s->redirect; + + return 0; +} +#endif +static handler_t mod_redirect_uri_handler(server *srv, connection *con, void *p_data) { +#ifdef HAVE_PCRE_H + plugin_data *p = p_data; + size_t i; + + /* + * REWRITE URL + * + * e.g. redirect /base/ to /index.php?section=base + * + */ + + mod_redirect_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_redirect_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + buffer_copy_string_buffer(p->match_buf, con->request.uri); + + for (i = 0; i < p->conf.redirect->used; i++) { + pcre *match; + const char *pattern; + size_t pattern_len; + int n; +# define N 10 + int ovec[N * 3]; + + match = p->conf.redirect->kv[i]->key; + pattern = p->conf.redirect->kv[i]->value; + pattern_len = strlen(pattern); + + if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) { + if (n != PCRE_ERROR_NOMATCH) { + log_error_write(srv, __FILE__, __LINE__, "sd" + "execution error while matching: ", n); + return HANDLER_ERROR; + } + } else { + const char **list; + size_t start, end; + size_t k; + /* it matched */ + pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list); + + /* search for $[0-9] */ + + buffer_reset(p->location); + + start = 0; end = pattern_len; + for (k = 0; k < pattern_len; k++) { + if (pattern[k] == '$' && + isdigit((unsigned char)pattern[k + 1])) { + /* got one */ + + size_t num = pattern[k + 1] - '0'; + + end = k; + + buffer_append_string_len(p->location, pattern + start, end - start); + + /* n is always > 0 */ + if (num < (size_t)n) { + buffer_append_string(p->location, list[num]); + } + + k++; + start = k + 1; + } + } + + buffer_append_string_len(p->location, pattern + start, pattern_len - start); + + pcre_free(list); + + response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->location)); + + con->http_status = 301; + + return HANDLER_FINISHED; + } + } +#undef N + +#else + UNUSED(srv); + UNUSED(con); + UNUSED(p_data); +#endif + + return HANDLER_GO_ON; +} + + +int mod_redirect_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("redirect"); + + p->init = mod_redirect_init; + p->handle_uri_clean = mod_redirect_uri_handler; + p->set_defaults = mod_redirect_set_defaults; + p->cleanup = mod_redirect_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_rewrite.c b/src/mod_rewrite.c new file mode 100644 index 00000000..868abb5f --- /dev/null +++ b/src/mod_rewrite.c @@ -0,0 +1,453 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +typedef struct { +#ifdef HAVE_PCRE_H + pcre *key; +#endif + + buffer *value; + + int final; +} rewrite_rule; + +typedef struct { + rewrite_rule **ptr; + + size_t used; + size_t size; +} rewrite_rule_buffer; + +typedef struct { + rewrite_rule_buffer *rewrite; +} plugin_config; + +typedef struct { + enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state; +} handler_ctx; + +typedef struct { + PLUGIN_DATA; + buffer *match_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +static handler_ctx * handler_ctx_init() { + handler_ctx * hctx; + + hctx = calloc(1, sizeof(*hctx)); + + hctx->state = REWRITE_STATE_UNSET; + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + free(hctx); +} + +rewrite_rule_buffer *rewrite_rule_buffer_init(void) { + rewrite_rule_buffer *kvb; + + kvb = calloc(1, sizeof(*kvb)); + + return kvb; +} + +int rewrite_rule_buffer_append(rewrite_rule_buffer *kvb, buffer *key, buffer *value, int final) { +#ifdef HAVE_PCRE_H + size_t i; + const char *errptr; + int erroff; + + if (!key) return -1; + + if (kvb->size == 0) { + kvb->size = 4; + kvb->used = 0; + + kvb->ptr = malloc(kvb->size * sizeof(*kvb->ptr)); + + for(i = 0; i < kvb->size; i++) { + kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr)); + } + } else if (kvb->used == kvb->size) { + kvb->size += 4; + + kvb->ptr = realloc(kvb->ptr, kvb->size * sizeof(*kvb->ptr)); + + for(i = kvb->used; i < kvb->size; i++) { + kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr)); + } + } + + if (NULL == (kvb->ptr[kvb->used]->key = pcre_compile(key->ptr, + 0, &errptr, &erroff, NULL))) { + + return -1; + } + + kvb->ptr[kvb->used]->value = buffer_init(); + buffer_copy_string_buffer(kvb->ptr[kvb->used]->value, value); + kvb->ptr[kvb->used]->final = final; + + kvb->used++; + + return 0; +#else + UNUSED(kvb); + UNUSED(value); + UNUSED(final); + UNUSED(key); + + return -1; +#endif +} + +void rewrite_rule_buffer_free(rewrite_rule_buffer *kvb) { +#ifdef HAVE_PCRE_H + size_t i; + + for (i = 0; i < kvb->size; i++) { + if (kvb->ptr[i]->key) pcre_free(kvb->ptr[i]->key); + if (kvb->ptr[i]->value) buffer_free(kvb->ptr[i]->value); + free(kvb->ptr[i]); + } + + if (kvb->ptr) free(kvb->ptr); +#endif + + free(kvb); +} + + +INIT_FUNC(mod_rewrite_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->match_buf = buffer_init(); + + return p; +} + +FREE_FUNC(mod_rewrite_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + buffer_free(p->match_buf); + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + rewrite_rule_buffer_free(s->rewrite); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_rewrite_set_defaults) { + plugin_data *p = p_d; + data_unset *du; + size_t i = 0; + + config_values_t cv[] = { + { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + /* 0 */ + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + size_t j; + array *ca; + data_array *da = (data_array *)du; + + s = malloc(sizeof(plugin_config)); + s->rewrite = rewrite_rule_buffer_init(); + + cv[0].destination = s->rewrite; + cv[1].destination = s->rewrite; + + p->config_storage[i] = s; + ca = ((data_config *)srv->config_context->data[i])->value; + + if (0 != config_insert_values_global(srv, ca, cv)) { + return HANDLER_ERROR; + } + + if (NULL != (du = array_get_element(ca, "url.rewrite"))) { + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", "url.rewrite", "array of strings"); + + return HANDLER_ERROR; + } + + da = (data_array *)du; + + for (j = 0; j < da->value->used; j++) { + if (da->value->data[j]->type != TYPE_STRING) { + log_error_write(srv, __FILE__, __LINE__, "sssbs", + "unexpected type for key: ", + "url.rewrite", + "[", da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + if (0 != rewrite_rule_buffer_append(s->rewrite, + ((data_string *)(da->value->data[j]))->key, + ((data_string *)(da->value->data[j]))->value, + 0)) { +#ifdef HAVE_PCRE_H + log_error_write(srv, __FILE__, __LINE__, "sb", + "pcre-compile failed for", da->value->data[j]->key); +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "pcre support is missing, please install libpcre and the headers"); +#endif + } + } + } + + if (NULL != (du = array_get_element(ca, "url.rewrite-final"))) { + if (du->type != TYPE_ARRAY) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "unexpected type for key: ", "url.rewrite", "array of strings"); + + return HANDLER_ERROR; + } + + da = (data_array *)du; + + for (j = 0; j < da->value->used; j++) { + if (da->value->data[j]->type != TYPE_STRING) { + log_error_write(srv, __FILE__, __LINE__, "sssbs", + "unexpected type for key: ", + "url.rewrite", + "[", da->value->data[j]->key, "](string)"); + + return HANDLER_ERROR; + } + + if (0 != rewrite_rule_buffer_append(s->rewrite, + ((data_string *)(da->value->data[j]))->key, + ((data_string *)(da->value->data[j]))->value, + 1)) { + +#ifdef HAVE_PCRE_H + log_error_write(srv, __FILE__, __LINE__, "sb", + "pcre-compile failed for", da->value->data[j]->key); +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "pcre support is missing, please install libpcre and the headers"); +#endif + } + } + } + } + + return HANDLER_GO_ON; +} +#ifdef HAVE_PCRE_H +static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) { + p->conf.rewrite = s->rewrite; + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) { + p->conf.rewrite = s->rewrite; + } + } + } + + return 0; +} + +static int mod_rewrite_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + + UNUSED(srv); + UNUSED(con); + + p->conf.rewrite = s->rewrite; + + return 0; +} +#endif +URIHANDLER_FUNC(mod_rewrite_con_reset) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (con->plugin_ctx[p->id]) { + handler_ctx_free(con->plugin_ctx[p->id]); + con->plugin_ctx[p->id] = NULL; + } + + return HANDLER_GO_ON; +} + +URIHANDLER_FUNC(mod_rewrite_uri_handler) { +#ifdef HAVE_PCRE_H + plugin_data *p = p_d; + size_t i; + handler_ctx *hctx; + + /* + * REWRITE URL + * + * e.g. rewrite /base/ to /index.php?section=base + * + */ + + if (con->plugin_ctx[p->id]) { + hctx = con->plugin_ctx[p->id]; + + if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON; + } + + mod_rewrite_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_rewrite_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + buffer_copy_string_buffer(p->match_buf, con->request.uri); + + for (i = 0; i < p->conf.rewrite->used; i++) { + pcre *match; + const char *pattern; + size_t pattern_len; + int n; + rewrite_rule *rule = p->conf.rewrite->ptr[i]; +# define N 10 + int ovec[N * 3]; + + match = rule->key; + pattern = rule->value->ptr; + pattern_len = rule->value->used - 1; + + if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) { + if (n != PCRE_ERROR_NOMATCH) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "execution error while matching: ", n); + return HANDLER_ERROR; + } + } else { + const char **list; + size_t start, end; + size_t k; + + /* it matched */ + pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list); + + /* search for $[0-9] */ + + buffer_reset(con->request.uri); + + start = 0; end = pattern_len; + for (k = 0; k < pattern_len; k++) { + if (pattern[k] == '$' && + isdigit((unsigned char)pattern[k + 1])) { + /* got one */ + + size_t num = pattern[k + 1] - '0'; + + end = k; + + buffer_append_string_len(con->request.uri, pattern + start, end - start); + + /* n is always larger than 0 */ + if (num < (size_t)n) { + buffer_append_string(con->request.uri, list[num]); + } + + k++; + start = k + 1; + } + } + + buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start); + + pcre_free(list); + + hctx = handler_ctx_init(); + + con->plugin_ctx[p->id] = hctx; + + if (rule->final) hctx->state = REWRITE_STATE_FINISHED; + + return HANDLER_COMEBACK; + } + } +#undef N + +#else + UNUSED(srv); + UNUSED(con); + UNUSED(p_d); +#endif + + return HANDLER_GO_ON; +} + +int mod_rewrite_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("rewrite"); + + p->init = mod_rewrite_init; + p->handle_uri_raw = mod_rewrite_uri_handler; + p->set_defaults = mod_rewrite_set_defaults; + p->cleanup = mod_rewrite_free; + p->connection_reset = mod_rewrite_con_reset; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_rrdtool.c b/src/mod_rrdtool.c new file mode 100644 index 00000000..1719d3ca --- /dev/null +++ b/src/mod_rrdtool.c @@ -0,0 +1,457 @@ +#define _GNU_SOURCE +#include <sys/types.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "server.h" +#include "connections.h" +#include "response.h" +#include "connections.h" +#include "log.h" + +#include "plugin.h" +#ifdef HAVE_FORK +/* no need for waitpid if we don't have fork */ +#include <sys/wait.h> +#endif +typedef struct { + buffer *path_rrdtool_bin; + buffer *path_rrd; + + double requests, *requests_ptr; + double bytes_written, *bytes_written_ptr; + double bytes_read, *bytes_read_ptr; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *cmd; + buffer *resp; + + int read_fd, write_fd; + pid_t rrdtool_pid; + + int rrdtool_running; + + plugin_config **config_storage; + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_rrd_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->resp = buffer_init(); + p->cmd = buffer_init(); + + return p; +} + +FREE_FUNC(mod_rrd_free) { + plugin_data *p = p_d; + size_t i; + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + buffer_free(s->path_rrdtool_bin); + buffer_free(s->path_rrd); + + free(s); + } + } + buffer_free(p->cmd); + buffer_free(p->resp); + + free(p->config_storage); + + if (p->rrdtool_pid) { + int status; + close(p->read_fd); + close(p->write_fd); +#ifdef HAVE_FORK + /* collect status */ + waitpid(p->rrdtool_pid, &status, 0); +#endif + } + + free(p); + + return HANDLER_GO_ON; +} + +int mod_rrd_create_pipe(server *srv, plugin_data *p) { + pid_t pid; + + int to_rrdtool_fds[2]; + int from_rrdtool_fds[2]; +#ifdef HAVE_FORK + if (pipe(to_rrdtool_fds)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "pipe failed: ", strerror(errno)); + return -1; + } + + if (pipe(from_rrdtool_fds)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "pipe failed: ", strerror(errno)); + return -1; + } + + /* fork, execve */ + switch (pid = fork()) { + case 0: { + /* child */ + char **args; + int argc; + int i = 0; + char *dash = "-"; + + /* move stdout to from_rrdtool_fd[1] */ + close(STDOUT_FILENO); + dup2(from_rrdtool_fds[1], STDOUT_FILENO); + close(from_rrdtool_fds[1]); + /* not needed */ + close(from_rrdtool_fds[0]); + + /* move the stdin to to_rrdtool_fd[0] */ + close(STDIN_FILENO); + dup2(to_rrdtool_fds[0], STDIN_FILENO); + close(to_rrdtool_fds[0]); + /* not needed */ + close(to_rrdtool_fds[1]); + + /* set up args */ + argc = 3; + args = malloc(sizeof(*args) * argc); + i = 0; + + args[i++] = p->conf.path_rrdtool_bin->ptr; + args[i++] = dash; + args[i++] = NULL; + + /* we don't need the client socket */ + for (i = 3; i < 256; i++) { + if (i != srv->log_error_fd) close(i); + } + + /* exec the cgi */ + execv(args[0], args); + + log_error_write(srv, __FILE__, __LINE__, "sss", "spawing rrdtool failed: ", strerror(errno), args[0]); + + /* */ + SEGFAULT(); + break; + } + case -1: + /* error */ + log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno)); + break; + default: { + /* father */ + + close(from_rrdtool_fds[1]); + close(to_rrdtool_fds[0]); + + /* register PID and wait for them asyncronously */ + p->write_fd = to_rrdtool_fds[1]; + p->read_fd = from_rrdtool_fds[0]; + p->rrdtool_pid = pid; + + break; + } + } + + return 0; +#else + return -1; +#endif +} + +static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, plugin_config *s) { + struct stat st; + + /* check if DB already exists */ + if (0 == stat(s->path_rrd->ptr, &st)) { + /* check if it is plain file */ + if (!S_ISREG(st.st_mode)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "not a regular file:", s->path_rrd); + return HANDLER_ERROR; + } + } else { + int r ; + /* create a new one */ + + BUFFER_COPY_STRING_CONST(p->cmd, "create "); + buffer_append_string_buffer(p->cmd, s->path_rrd); + buffer_append_string(p->cmd, " --step 60 "); + buffer_append_string(p->cmd, "DS:InOctets:ABSOLUTE:600:U:U "); + buffer_append_string(p->cmd, "DS:OutOctets:ABSOLUTE:600:U:U "); + buffer_append_string(p->cmd, "DS:Requests:ABSOLUTE:600:U:U "); + buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:1:600 "); + buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:6:700 "); + buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:24:775 "); + buffer_append_string(p->cmd, "RRA:AVERAGE:0.5:288:797 "); + buffer_append_string(p->cmd, "RRA:MAX:0.5:1:600 "); + buffer_append_string(p->cmd, "RRA:MAX:0.5:6:700 "); + buffer_append_string(p->cmd, "RRA:MAX:0.5:24:775 "); + buffer_append_string(p->cmd, "RRA:MAX:0.5:288:797 "); + buffer_append_string(p->cmd, "RRA:MIN:0.5:1:600 "); + buffer_append_string(p->cmd, "RRA:MIN:0.5:6:700 "); + buffer_append_string(p->cmd, "RRA:MIN:0.5:24:775 "); + buffer_append_string(p->cmd, "RRA:MIN:0.5:288:797\n"); + + if (-1 == (r = write(p->write_fd, p->cmd->ptr, p->cmd->used - 1))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "rrdtool-write: failed", strerror(errno)); + + return HANDLER_ERROR; + } + + buffer_prepare_copy(p->resp, 4096); + if (-1 == (r = read(p->read_fd, p->resp->ptr, p->resp->size))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "rrdtool-read: failed", strerror(errno)); + + return HANDLER_ERROR; + } + + p->resp->used = r; + + if (p->resp->ptr[0] != 'O' || + p->resp->ptr[1] != 'K') { + log_error_write(srv, __FILE__, __LINE__, "sbb", + "rrdtool-response:", p->cmd, p->resp); + + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_rrd_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("rrdtool.db-name"))) { + PATCH(path_rrd); + /* get pointers to double values */ + + p->conf.bytes_written_ptr = &(s->bytes_written); + p->conf.bytes_read_ptr = &(s->bytes_read); + p->conf.requests_ptr = &(s->requests); + } + } + } + + return 0; +} + +static int mod_rrd_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(path_rrdtool_bin); + PATCH(path_rrd); + + p->conf.bytes_written_ptr = &(s->bytes_written); + p->conf.bytes_read_ptr = &(s->bytes_read); + p->conf.requests_ptr = &(s->requests); + + return 0; +} +#undef PATCH + +SETDEFAULTS_FUNC(mod_rrd_set_defaults) { + plugin_data *p = p_d; + size_t i; + + config_values_t cv[] = { + { "rrdtool.binary", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_SERVER }, + { "rrdtool.db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->path_rrdtool_bin = buffer_init(); + s->path_rrd = buffer_init(); + s->requests = 0; + s->bytes_written = 0; + s->bytes_read = 0; + + cv[0].destination = s->path_rrdtool_bin; + cv[1].destination = s->path_rrd; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + + if (i > 0 && !buffer_is_empty(s->path_rrdtool_bin)) { + /* path_rrdtool_bin is a global option */ + + log_error_write(srv, __FILE__, __LINE__, "s", + "rrdtool.binary can only be set as a global option."); + + return HANDLER_ERROR; + } + + } + + p->conf.path_rrdtool_bin = p->config_storage[0]->path_rrdtool_bin; + p->rrdtool_running = 0; + + /* check for dir */ + + if (buffer_is_empty(p->conf.path_rrdtool_bin)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "rrdtool.binary has to be set"); + return HANDLER_ERROR; + } + + /* open the pipe to rrdtool */ + if (mod_rrd_create_pipe(srv, p)) { + return HANDLER_ERROR; + } + + p->rrdtool_running = 1; + + return HANDLER_GO_ON; +} + +TRIGGER_FUNC(mod_rrd_trigger) { + plugin_data *p = p_d; + size_t i; + + if (!p->rrdtool_running) return HANDLER_GO_ON; + if ((srv->cur_ts % 60) != 0) return HANDLER_GO_ON; + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + int r; + + if (buffer_is_empty(s->path_rrd)) continue; + + /* write the data down every minute */ + + if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s)) return HANDLER_ERROR; + + BUFFER_COPY_STRING_CONST(p->cmd, "update "); + buffer_append_string_buffer(p->cmd, s->path_rrd); + BUFFER_APPEND_STRING_CONST(p->cmd, " N:"); + buffer_append_off_t(p->cmd, s->bytes_read); + BUFFER_APPEND_STRING_CONST(p->cmd, ":"); + buffer_append_off_t(p->cmd, s->bytes_written); + BUFFER_APPEND_STRING_CONST(p->cmd, ":"); + buffer_append_long(p->cmd, s->requests); + BUFFER_APPEND_STRING_CONST(p->cmd, "\n"); + + if (-1 == (r = write(p->write_fd, p->cmd->ptr, p->cmd->used - 1))) { + p->rrdtool_running = 0; + + log_error_write(srv, __FILE__, __LINE__, "ss", + "rrdtool-write: failed", strerror(errno)); + + return HANDLER_ERROR; + } + + buffer_prepare_copy(p->resp, 4096); + if (-1 == (r = read(p->read_fd, p->resp->ptr, p->resp->size))) { + p->rrdtool_running = 0; + + log_error_write(srv, __FILE__, __LINE__, "ss", + "rrdtool-read: failed", strerror(errno)); + + return HANDLER_ERROR; + } + + p->resp->used = r; + + if (p->resp->ptr[0] != 'O' || + p->resp->ptr[1] != 'K') { + p->rrdtool_running = 0; + + log_error_write(srv, __FILE__, __LINE__, "sbb", + "rrdtool-response:", p->cmd, p->resp); + + return HANDLER_ERROR; + } + s->requests = 0; + s->bytes_written = 0; + s->bytes_read = 0; + } + + return HANDLER_GO_ON; +} + +REQUESTDONE_FUNC(mod_rrd_account) { + plugin_data *p = p_d; + size_t i; + + mod_rrd_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_rrd_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + *(p->conf.requests_ptr) += 1; + *(p->conf.bytes_written_ptr) += con->bytes_written; + *(p->conf.bytes_read_ptr) += con->bytes_read; + + return HANDLER_GO_ON; +} + +int mod_rrdtool_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("rrd"); + + p->init = mod_rrd_init; + p->cleanup = mod_rrd_free; + p->set_defaults= mod_rrd_set_defaults; + + p->handle_trigger = mod_rrd_trigger; + p->handle_request_done = mod_rrd_account; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_secure_download.c b/src/mod_secure_download.c new file mode 100644 index 00000000..eecc230a --- /dev/null +++ b/src/mod_secure_download.c @@ -0,0 +1,323 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef USE_OPENSSL +# include <openssl/md5.h> +#else +# include "md5_global.h" +# include "md5.h" +#endif + +#define HASHLEN 16 +typedef unsigned char HASH[HASHLEN]; +#define HASHHEXLEN 32 +typedef char HASHHEX[HASHHEXLEN+1]; +#ifdef USE_OPENSSL +#define IN const +#else +#define IN +#endif +#define OUT + + +/* plugin config for all request/connections */ + +typedef struct { + buffer *doc_root; + buffer *secret; + buffer *uri_prefix; + + time_t timeout; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *md5; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_secdownload_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->md5 = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_secdownload_free) { + plugin_data *p = p_d; + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + buffer_free(s->secret); + buffer_free(s->doc_root); + buffer_free(s->uri_prefix); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->md5); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_secdownload_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "secdownload.secret", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "secdownload.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "secdownload.uri-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "secdownload.timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->secret = buffer_init(); + s->doc_root = buffer_init(); + s->uri_prefix = buffer_init(); + s->timeout = 0; + + /* set global default */ + if (i == 0) { + s->timeout = 60; + buffer_copy_string(s->uri_prefix, "/"); + } + + cv[0].destination = s->secret; + cv[1].destination = s->doc_root; + cv[2].destination = s->uri_prefix; + cv[3].destination = &(s->timeout); + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +/** + * checks if the supplied string is a MD5 string + * + * @param str a possible MD5 string + * @return if the supplied string is a valid MD5 string 1 is returned otherwise 0 + */ + +int is_hex_len(const char *str, size_t len) { + size_t i; + + if (NULL == str) return 0; + + for (i = 0; i < len && *str; i++, str++) { + /* illegal characters */ + if (!((*str >= '0' && *str <= '9') || + (*str >= 'a' && *str <= 'f') || + (*str >= 'A' && *str <= 'F')) + ) { + return 0; + } + } + + return i == len; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_secdownload_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.secret"))) { + PATCH(secret); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.document-root"))) { + PATCH(doc_root); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.uri-prefix"))) { + PATCH(uri_prefix); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.timeout"))) { + PATCH(timeout); + } + } + } + + return 0; +} + +static int mod_secdownload_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(secret); + PATCH(doc_root); + PATCH(uri_prefix); + PATCH(timeout); + + return 0; +} +#undef PATCH + + +URIHANDLER_FUNC(mod_secdownload_uri_handler) { + plugin_data *p = p_d; + MD5_CTX Md5Ctx; + HASH HA1; + const char *rel_uri, *ts_str, *md5_str; + time_t ts = 0; + size_t i; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_secdownload_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_secdownload_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + if (buffer_is_empty(p->conf.secret)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "secdownload.secret has to be set"); + return HANDLER_ERROR; + } + + if (buffer_is_empty(p->conf.doc_root)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "secdownload.document-root has to be set"); + return HANDLER_ERROR; + } + + /* + * /<uri-prefix>[a-f0-9]{32}/[a-f0-9]{8}/<rel-path> + */ + + if (0 != strncmp(con->uri.path->ptr, p->conf.uri_prefix->ptr, p->conf.uri_prefix->used - 1)) return HANDLER_GO_ON; + + md5_str = con->uri.path->ptr + p->conf.uri_prefix->used - 1; + + if (!is_hex_len(md5_str, 32)) return HANDLER_GO_ON; + if (*(md5_str + 32) != '/') return HANDLER_GO_ON; + + ts_str = md5_str + 32 + 1; + + if (!is_hex_len(ts_str, 8)) return HANDLER_GO_ON; + if (*(ts_str + 8) != '/') return HANDLER_GO_ON; + + for (i = 0; i < 8; i++) { + ts = (ts << 4) + hex2int(*(ts_str + i)); + } + + /* timed-out */ + if (srv->cur_ts - ts > p->conf.timeout || + srv->cur_ts - ts < -p->conf.timeout) { + con->http_status = 408; + + return HANDLER_FINISHED; + } + + rel_uri = ts_str + 8; + + /* checking MD5 + * + * <secret><rel-path><timestamp-hex> + */ + + buffer_copy_string_buffer(p->md5, p->conf.secret); + buffer_append_string(p->md5, rel_uri); + buffer_append_string_len(p->md5, ts_str, 8); + + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)p->md5->ptr, p->md5->used - 1); + MD5_Final(HA1, &Md5Ctx); + + buffer_copy_string_hex(p->md5, (char *)HA1, 16); + + if (0 != strncmp(md5_str, p->md5->ptr, 32)) { + con->http_status = 403; + + log_error_write(srv, __FILE__, __LINE__, "sss", + "md5 invalid:", + md5_str, p->md5->ptr); + + return HANDLER_FINISHED; + } + + /* starting with the last / we should have relative-path to the docroot + */ + + buffer_copy_string_buffer(con->physical.path, p->conf.doc_root); + buffer_append_string(con->physical.path, rel_uri); + + return HANDLER_COMEBACK; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_secdownload_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("secdownload"); + + p->init = mod_secdownload_init; + p->handle_uri_clean = mod_secdownload_uri_handler; + p->set_defaults = mod_secdownload_set_defaults; + p->cleanup = mod_secdownload_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_setenv.c b/src/mod_setenv.c new file mode 100644 index 00000000..8cfca42e --- /dev/null +++ b/src/mod_setenv.c @@ -0,0 +1,215 @@ +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "config.h" + +#include "response.h" + +/* plugin config for all request/connections */ + +typedef struct { + array *request_header; + array *response_header; + + array *environment; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_setenv_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_setenv_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->request_header); + array_free(s->response_header); + array_free(s->environment); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_setenv_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "setenv.add-request-header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "setenv.add-response-header", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "setenv.add-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->request_header = array_init(); + s->response_header = array_init(); + s->environment = array_init(); + + cv[0].destination = s->request_header; + cv[1].destination = s->response_header; + cv[2].destination = s->environment; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_setenv_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-request-header"))) { + PATCH(request_header); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.add-response-header"))) { + PATCH(response_header); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("setenv.environment"))) { + PATCH(environment); + } + } + } + + return 0; +} + +static int mod_setenv_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(request_header); + PATCH(response_header); + PATCH(environment); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_setenv_uri_handler) { + plugin_data *p = p_d; + size_t k, i; + + mod_setenv_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_setenv_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + for (k = 0; k < p->conf.request_header->used; k++) { + data_string *ds = (data_string *)p->conf.request_header->data[k]; + data_string *ds_dst; + + if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) { + ds_dst = data_string_init(); + } + + buffer_copy_string_buffer(ds_dst->key, ds->key); + buffer_copy_string_buffer(ds_dst->value, ds->value); + + array_insert_unique(con->request.headers, (data_unset *)ds_dst); + } + + for (k = 0; k < p->conf.environment->used; k++) { + data_string *ds = (data_string *)p->conf.environment->data[k]; + data_string *ds_dst; + + if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds_dst = data_string_init(); + } + + buffer_copy_string_buffer(ds_dst->key, ds->key); + buffer_copy_string_buffer(ds_dst->value, ds->value); + + array_insert_unique(con->environment, (data_unset *)ds_dst); + } + + for (k = 0; k < p->conf.response_header->used; k++) { + data_string *ds = (data_string *)p->conf.response_header->data[k]; + + response_header_insert(srv, con, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value)); + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_setenv_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("setenv"); + + p->init = mod_setenv_init; + p->handle_uri_clean = mod_setenv_uri_handler; + p->set_defaults = mod_setenv_set_defaults; + p->cleanup = mod_setenv_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_simple_vhost.c b/src/mod_simple_vhost.c new file mode 100644 index 00000000..9fcef860 --- /dev/null +++ b/src/mod_simple_vhost.c @@ -0,0 +1,284 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" +#include "file_cache.h" + +#include "plugin.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +typedef struct { + buffer *server_root; + buffer *default_host; + buffer *document_root; + + buffer *docroot_cache_key; + buffer *docroot_cache_value; + buffer *docroot_cache_servername; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *doc_root; + + plugin_config **config_storage; + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_simple_vhost_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->doc_root = buffer_init(); + + return p; +} + +FREE_FUNC(mod_simple_vhost_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + buffer_free(s->document_root); + buffer_free(s->default_host); + buffer_free(s->server_root); + + buffer_free(s->docroot_cache_key); + buffer_free(s->docroot_cache_value); + buffer_free(s->docroot_cache_servername); + + free(s); + } + + free(p->config_storage); + } + + buffer_free(p->doc_root); + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_simple_vhost_set_defaults) { + plugin_data *p = p_d; + size_t i; + + config_values_t cv[] = { + { "simple-vhost.server-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "simple-vhost.default-host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "simple-vhost.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = calloc(1, sizeof(plugin_config)); + + s->server_root = buffer_init(); + s->default_host = buffer_init(); + s->document_root = buffer_init(); + + s->docroot_cache_key = buffer_init(); + s->docroot_cache_value = buffer_init(); + s->docroot_cache_servername = buffer_init(); + + cv[0].destination = s->server_root; + cv[1].destination = s->default_host; + cv[2].destination = s->document_root; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +static int build_doc_root(server *srv, connection *con, plugin_data *p, buffer *out, buffer *host) { + buffer_prepare_copy(out, 128); + + if (p->conf.server_root->used) { + buffer_copy_string_buffer(out, p->conf.server_root); + + if (host->used) { + /* a hostname has to start with a alpha-numerical character + * and must not contain a slash "/" + */ + char *dp; + + BUFFER_APPEND_SLASH(out); + + if (NULL == (dp = strchr(host->ptr, ':'))) { + buffer_append_string_buffer(out, host); + } else { + buffer_append_string_len(out, host->ptr, dp - host->ptr); + } + } + BUFFER_APPEND_SLASH(out); + + if (p->conf.document_root->used > 2 && p->conf.document_root->ptr[0] == '/') { + buffer_append_string_len(out, p->conf.document_root->ptr + 1, p->conf.document_root->used - 2); + } else { + buffer_append_string_buffer(out, p->conf.document_root); + BUFFER_APPEND_SLASH(out); + } + } else { + buffer_copy_string_buffer(out, con->conf.document_root); + BUFFER_APPEND_SLASH(out); + } + + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, out, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), out); + return -1; + } + + if (!S_ISDIR(con->fce->st.st_mode)) { + return -1; + } + + return 0; +} + + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_simple_vhost_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.server-root"))) { + PATCH(server_root); + PATCH(docroot_cache_key); + PATCH(docroot_cache_value); + PATCH(docroot_cache_servername); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.default-host"))) { + PATCH(default_host); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("simple-vhost.document-root"))) { + PATCH(document_root); + } + } + } + + return 0; +} + +static int mod_simple_vhost_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(server_root); + PATCH(default_host); + PATCH(document_root); + + PATCH(docroot_cache_key); + PATCH(docroot_cache_value); + PATCH(docroot_cache_servername); + + return 0; +} +#undef PATCH + +static handler_t mod_simple_vhost_docroot(server *srv, connection *con, void *p_data) { + plugin_data *p = p_data; + size_t i; + + /* + * cache the last successfull translation from hostname (authority) to docroot + * - this saves us a stat() call + * + */ + + mod_simple_vhost_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_simple_vhost_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + if (p->conf.docroot_cache_key->used && + con->uri.authority->used && + buffer_is_equal(p->conf.docroot_cache_key, con->uri.authority)) { + /* cache hit */ + buffer_copy_string_buffer(con->physical.doc_root, p->conf.docroot_cache_value); + buffer_copy_string_buffer(con->server_name, p->conf.docroot_cache_servername); + } else { + /* build document-root */ + if ((con->uri.authority->used == 0) || + build_doc_root(srv, con, p, p->doc_root, con->uri.authority)) { + /* not found, fallback the default-host */ + if (build_doc_root(srv, con, p, + p->doc_root, + p->conf.default_host)) { + return HANDLER_GO_ON; + } else { + buffer_copy_string_buffer(con->server_name, p->conf.default_host); + } + } else { + buffer_copy_string_buffer(con->server_name, con->uri.authority); + } + + /* copy to cache */ + buffer_copy_string_buffer(p->conf.docroot_cache_key, con->uri.authority); + buffer_copy_string_buffer(p->conf.docroot_cache_value, p->doc_root); + buffer_copy_string_buffer(p->conf.docroot_cache_servername, con->server_name); + + buffer_copy_string_buffer(con->physical.doc_root, p->doc_root); + } + + return HANDLER_GO_ON; +} + + +int mod_simple_vhost_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("simple_vhost"); + + p->init = mod_simple_vhost_init; + p->set_defaults = mod_simple_vhost_set_defaults; + p->handle_docroot = mod_simple_vhost_docroot; + p->cleanup = mod_simple_vhost_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_skeleton.c b/src/mod_skeleton.c new file mode 100644 index 00000000..b5dab829 --- /dev/null +++ b/src/mod_skeleton.c @@ -0,0 +1,220 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#include "config.h" + +/** + * this is a skeleton for a lighttpd plugin + * + * just replaces every occurance of 'skeleton' by your plugin name + * + * e.g. in vim: + * + * :%s/skeleton/myhandler/ + * + */ + + + +/* plugin config for all request/connections */ + +typedef struct { + array *match; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *match_buf; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +typedef struct { + size_t foo; +} handler_ctx; + +static handler_ctx * handler_ctx_init() { + handler_ctx * hctx; + + hctx = calloc(1, sizeof(*hctx)); + + return hctx; +} + +static void handler_ctx_free(handler_ctx *hctx) { + + free(hctx); +} + +/* init the plugin data */ +INIT_FUNC(mod_skeleton_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->match_buf = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_skeleton_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->match); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->match_buf); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_skeleton_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "skeleton.array", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->match = array_init(); + + cv[0].destination = s->match; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_skeleton_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("skeleton.array"))) { + PATCH(match); + } + } + } + + return 0; +} + +static int mod_skeleton_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(match); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_skeleton_uri_handler) { + plugin_data *p = p_d; + int s_len; + size_t k, i; + + UNUSED(srv); + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_skeleton_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_skeleton_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + s_len = con->uri.path->used - 1; + + for (k = 0; k < p->conf.match->used; k++) { + data_string *ds = (data_string *)p->conf.match->data[k]; + int ct_len = ds->value->used - 1; + + if (ct_len > s_len) continue; + if (ds->value->used == 0) continue; + + if (0 == strncmp(con->uri.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) { + con->http_status = 403; + + return HANDLER_FINISHED; + } + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_skeleton_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("skeleton"); + + p->init = mod_skeleton_init; + p->handle_uri_clean = mod_skeleton_uri_handler; + p->set_defaults = mod_skeleton_set_defaults; + p->cleanup = mod_skeleton_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_ssi.c b/src/mod_ssi.c new file mode 100644 index 00000000..038c08e6 --- /dev/null +++ b/src/mod_ssi.c @@ -0,0 +1,1073 @@ +#include <sys/types.h> + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <unistd.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" +#include "stream.h" + +#include "response.h" + +#include "mod_ssi.h" + +#include "inet_ntop_cache.h" + +#include "sys-socket.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_FORK +#include <sys/wait.h> +#endif +/* init the plugin data */ +INIT_FUNC(mod_ssi_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->timefmt = buffer_init(); + p->stat_fn = buffer_init(); + + p->ssi_vars = array_init(); + p->ssi_cgi_env = array_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_ssi_free) { + plugin_data *p = p_d; + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->ssi_extension); + + free(s); + } + free(p->config_storage); + } + + array_free(p->ssi_vars); + array_free(p->ssi_cgi_env); +#ifdef HAVE_PCRE_H + pcre_free(p->ssi_regex); +#endif + buffer_free(p->timefmt); + buffer_free(p->stat_fn); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_ssi_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; +#ifdef HAVE_PCRE_H + const char *errptr; + int erroff; +#endif + + config_values_t cv[] = { + { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->ssi_extension = array_init(); + + cv[0].destination = s->ssi_extension; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + +#ifdef HAVE_PCRE_H + /* allow 2 params */ + if (NULL == (p->ssi_regex = pcre_compile("<!--#([a-z]+)\\s+(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?-->", 0, &errptr, &erroff, NULL))) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "ssi: pcre ", + erroff, errptr); + return HANDLER_ERROR; + } +#else + log_error_write(srv, __FILE__, __LINE__, "s", + "mod_ssi: pcre support is missing, please recompile with pcre support or remove mod_ssi from the list of modules"); + return HANDLER_ERROR; +#endif + + return HANDLER_GO_ON; +} + +int ssi_env_add(array *env, const char *key, const char *val) { + data_string *ds; + + if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) { + ds = data_string_init(); + } + buffer_copy_string(ds->key, key); + buffer_copy_string(ds->value, val); + + array_insert_unique(env, (data_unset *)ds); + + return 0; +} + +/** + * + * the next two functions are take from fcgi.c + * + */ + +static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) { + size_t i; + + for (i = 0; i < con->request.headers->used; i++) { + data_string *ds; + + ds = (data_string *)con->request.headers->data[i]; + + if (ds->value->used && ds->key->used) { + size_t j; + buffer_reset(srv->tmp_buf); + + /* don't forward the Authorization: Header */ + if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) { + continue; + } + + if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { + buffer_copy_string(srv->tmp_buf, "HTTP_"); + srv->tmp_buf->used--; + } + + buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); + for (j = 0; j < ds->key->used - 1; j++) { + srv->tmp_buf->ptr[srv->tmp_buf->used++] = + isalpha((unsigned char)ds->key->ptr[j]) ? + toupper((unsigned char)ds->key->ptr[j]) : '_'; + } + srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0'; + + ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr); + } + } + + return 0; +} + +static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) { + char buf[32]; + + server_socket *srv_sock = con->srv_socket; + +#ifdef HAVE_IPV6 + char b2[INET6_ADDRSTRLEN + 1]; +#endif + +#define CONST_STRING(x) \ + x + + array_reset(p->ssi_cgi_env); + +#ifdef PACKAGE_NAME + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_NAME"/"PACKAGE_VERSION); +#else + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE"/"VERSION); +#endif + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"), +#ifdef HAVE_IPV6 + inet_ntop(srv_sock->addr.plain.sa_family, + srv_sock->addr.plain.sa_family == AF_INET6 ? + (const void *) &(srv_sock->addr.ipv6.sin6_addr) : + (const void *) &(srv_sock->addr.ipv4.sin_addr), + b2, sizeof(b2)-1) +#else + inet_ntoa(srv_sock->addr.ipv4.sin_addr) +#endif + ); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1"); + + ltostr(buf, +#ifdef HAVE_IPV6 + ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port) +#else + ntohs(srv_sock->addr.ipv4.sin_port) +#endif + ); + + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf); + + ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"), + inet_ntop_cache_get_ip(srv, &(con->dst_addr))); + + if (con->authed_user->used) { + ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_USER"), + con->authed_user->ptr); + } + + if (con->request.content_length > 0) { + /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */ + + /* request.content_length < SSIZE_MAX, see request.c */ + ltostr(buf, con->request.content_length); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf); + } + + /* + * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to + * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html + * (6.1.14, 6.1.6, 6.1.7) + */ + + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), ""); + + /* + * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual + * http://www.php.net/manual/en/reserved.variables.php + * treatment of PATH_TRANSLATED is different from the one of CGI specs. + * TODO: this code should be checked against cgi.fix_pathinfo php + * parameter. + */ + + if (con->request.pathinfo->used) { + ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr); + } + + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.doc_root->ptr); + + ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : ""); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method)); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200"); + ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version)); + + ssi_env_add_request_headers(srv, con, p); + + return 0; +} + +static int process_ssi_stmt(server *srv, connection *con, plugin_data *p, + const char **l, size_t n) { + size_t i, ssicmd = 0; + char buf[255]; + buffer *b = NULL; + + struct { + const char *var; + enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD, + SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF, + SSI_ELSE, SSI_ENDIF, SSI_EXEC } type; + } ssicmds[] = { + { "echo", SSI_ECHO }, + { "include", SSI_INCLUDE }, + { "flastmod", SSI_FLASTMOD }, + { "fsize", SSI_FSIZE }, + { "config", SSI_CONFIG }, + { "printenv", SSI_PRINTENV }, + { "set", SSI_SET }, + { "if", SSI_IF }, + { "elif", SSI_ELIF }, + { "endif", SSI_ENDIF }, + { "else", SSI_ELSE }, + { "exec", SSI_EXEC }, + + { NULL, SSI_UNSET } + }; + + for (i = 0; ssicmds[i].var; i++) { + if (0 == strcmp(l[1], ssicmds[i].var)) { + ssicmd = ssicmds[i].type; + break; + } + } + + switch(ssicmd) { + case SSI_ECHO: { + /* echo */ + int var = 0, enc = 0; + const char *var_val = NULL; + + struct { + const char *var; + enum { SSI_ECHO_UNSET, SSI_ECHO_DATE_GMT, SSI_ECHO_DATE_LOCAL, SSI_ECHO_DOCUMENT_NAME, SSI_ECHO_DOCUMENT_URI, + SSI_ECHO_LAST_MODIFIED, SSI_ECHO_USER_NAME } type; + } echovars[] = { + { "DATE_GMT", SSI_ECHO_DATE_GMT }, + { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL }, + { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME }, + { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI }, + { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED }, + { "USER_NAME", SSI_ECHO_USER_NAME }, + + { NULL, SSI_ECHO_UNSET } + }; + + struct { + const char *var; + enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type; + } encvars[] = { + { "url", SSI_ENC_URL }, + { "none", SSI_ENC_NONE }, + { "entity", SSI_ENC_ENTITY }, + + { NULL, SSI_ENC_UNSET } + }; + + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "var")) { + int j; + + var_val = l[i+1]; + + for (j = 0; echovars[j].var; j++) { + if (0 == strcmp(l[i+1], echovars[j].var)) { + var = echovars[j].type; + break; + } + } + } else if (0 == strcmp(l[i], "encoding")) { + int j; + + for (j = 0; encvars[j].var; j++) { + if (0 == strcmp(l[i+1], encvars[j].var)) { + enc = encvars[j].type; + break; + } + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + + if (p->if_is_false) break; + + if (!var_val) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: ", + l[1], "var is missing"); + break; + } + + switch(var) { + case SSI_ECHO_USER_NAME: { + struct passwd *pw; + + b = chunkqueue_get_append_buffer(con->write_queue); +#ifdef HAVE_PWD_H + if (NULL == (pw = getpwuid(con->fce->st.st_uid))) { + buffer_copy_long(b, con->fce->st.st_uid); + } else { + buffer_copy_string(b, pw->pw_name); + } +#else + buffer_copy_long(b, con->fce->st.st_uid); +#endif + break; + } + case SSI_ECHO_LAST_MODIFIED: { + time_t t = con->fce->st.st_mtime; + + b = chunkqueue_get_append_buffer(con->write_queue); + if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { + buffer_copy_string(b, "(none)"); + } else { + buffer_copy_string(b, buf); + } + break; + } + case SSI_ECHO_DATE_LOCAL: { + time_t t = time(NULL); + + b = chunkqueue_get_append_buffer(con->write_queue); + if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { + buffer_copy_string(b, "(none)"); + } else { + buffer_copy_string(b, buf); + } + break; + } + case SSI_ECHO_DATE_GMT: { + time_t t = time(NULL); + + b = chunkqueue_get_append_buffer(con->write_queue); + if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) { + buffer_copy_string(b, "(none)"); + } else { + buffer_copy_string(b, buf); + } + break; + } + case SSI_ECHO_DOCUMENT_NAME: { + char *sl; + + b = chunkqueue_get_append_buffer(con->write_queue); + if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) { + buffer_copy_string_buffer(b, con->physical.path); + } else { + buffer_copy_string(b, sl + 1); + } + break; + } + case SSI_ECHO_DOCUMENT_URI: { + b = chunkqueue_get_append_buffer(con->write_queue); + buffer_copy_string_buffer(b, con->uri.path); + break; + } + default: { + data_string *ds; + /* check if it is a cgi-var */ + + b = chunkqueue_get_append_buffer(con->write_queue); + + if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val))) { + buffer_copy_string_buffer(b, ds->value); + } else { + buffer_copy_string(b, "(none)"); + } + + break; + } + } + break; + } + case SSI_INCLUDE: + case SSI_FLASTMOD: + case SSI_FSIZE: { + const char * file_path = NULL, *virt_path = NULL; + struct stat st; + char *sl; + + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "file")) { + file_path = l[i+1]; + } else if (0 == strcmp(l[i], "virtual")) { + virt_path = l[i+1]; + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + + if (!file_path && !virt_path) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: ", + l[1], "file or virtual are missing"); + break; + } + + if (file_path && virt_path) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: ", + l[1], "only one of file and virtual is allowed here"); + break; + } + + + if (p->if_is_false) break; + + if (file_path) { + /* current doc-root */ + if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) { + buffer_copy_string(p->stat_fn, "/"); + } else { + buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1); + } + + /* fn */ + if (NULL == (sl = strrchr(file_path, '/'))) { + buffer_append_string(p->stat_fn, file_path); + } else { + buffer_append_string(p->stat_fn, sl + 1); + } + } else { + /* virtual */ + + if (virt_path[0] == '/') { + buffer_copy_string(p->stat_fn, virt_path); + } else { + /* there is always a / */ + sl = strrchr(con->uri.path->ptr, '/'); + + buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1); + buffer_append_string(p->stat_fn, virt_path); + } + + buffer_urldecode(p->stat_fn); + buffer_path_simplify(&(srv->dot_stack), srv->tmp_buf, p->stat_fn); + + /* we have an uri */ + + buffer_copy_string_buffer(p->stat_fn, con->physical.doc_root); + buffer_append_string_buffer(p->stat_fn, srv->tmp_buf); + } + + if (0 == stat(p->stat_fn->ptr, &st)) { + time_t t = st.st_mtime; + + switch (ssicmd) { + case SSI_FSIZE: + b = chunkqueue_get_append_buffer(con->write_queue); + if (p->sizefmt) { + int j = 0; + const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL }; + + off_t s = st.st_size; + + for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++); + + buffer_copy_off_t(b, s); + buffer_append_string(b, abr[j]); + } else { + buffer_copy_off_t(b, st.st_size); + } + break; + case SSI_FLASTMOD: + b = chunkqueue_get_append_buffer(con->write_queue); + if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { + buffer_copy_string(b, "(none)"); + } else { + buffer_copy_string(b, buf); + } + break; + case SSI_INCLUDE: + chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size); + break; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "ssi: stating failed ", + p->stat_fn, strerror(errno)); + } + break; + } + case SSI_SET: { + const char *key = NULL, *val = NULL; + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "var")) { + key = l[i+1]; + } else if (0 == strcmp(l[i], "value")) { + val = l[i+1]; + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + + if (p->if_is_false) break; + + if (key && val) { + data_string *ds; + + if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) { + ds = data_string_init(); + } + buffer_copy_string(ds->key, key); + buffer_copy_string(ds->value, val); + + array_insert_unique(p->ssi_vars, (data_unset *)ds); + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: var and value have to be set in", + l[0], l[1]); + } + break; + } + case SSI_CONFIG: + if (p->if_is_false) break; + + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "timefmt")) { + buffer_copy_string(p->timefmt, l[i+1]); + } else if (0 == strcmp(l[i], "sizefmt")) { + if (0 == strcmp(l[i+1], "abbrev")) { + p->sizefmt = 1; + } else if (0 == strcmp(l[i+1], "abbrev")) { + p->sizefmt = 0; + } else { + log_error_write(srv, __FILE__, __LINE__, "sssss", + "ssi: unknow value for attribute '", + l[i], + "' for ", + l[1], l[i+1]); + } + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + break; + case SSI_PRINTENV: + if (p->if_is_false) break; + + b = chunkqueue_get_append_buffer(con->write_queue); + buffer_copy_string(b, "<pre>"); + for (i = 0; i < p->ssi_vars->used; i++) { + data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]]; + + buffer_append_string_buffer(b, ds->key); + buffer_append_string(b, ": "); + buffer_append_string_buffer(b, ds->value); + buffer_append_string(b, "<br />"); + + } + buffer_append_string(b, "</pre>"); + + break; + case SSI_EXEC: { + const char *cmd = NULL; + pid_t pid; + int from_exec_fds[2]; + + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "cmd")) { + cmd = l[i+1]; + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + + if (p->if_is_false) break; + + /* create a return pipe and send output to the html-page + * + * as exec is assumed evil it is implemented synchronously + */ + + if (!cmd) break; +#ifdef HAVE_FORK + if (pipe(from_exec_fds)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "pipe failed: ", strerror(errno)); + return -1; + } + + /* fork, execve */ + switch (pid = fork()) { + case 0: { + /* move stdout to from_rrdtool_fd[1] */ + close(STDOUT_FILENO); + dup2(from_exec_fds[1], STDOUT_FILENO); + close(from_exec_fds[1]); + /* not needed */ + close(from_exec_fds[0]); + + /* close stdin */ + close(STDIN_FILENO); + + execl("/bin/sh", "sh", "-c", cmd, NULL); + + log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd); + + /* */ + SEGFAULT(); + break; + } + case -1: + /* error */ + log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); + break; + default: { + /* father */ + int status; + ssize_t r; + + close(from_exec_fds[1]); + + /* wait for the client to end */ + if (-1 == waitpid(pid, &status, 0)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno)); + } else if (WIFEXITED(status)) { + + /* read everything from client and paste it into the output */ + + for (b = chunkqueue_get_append_buffer(con->write_queue), buffer_prepare_copy(b, 4096); + (r = read(from_exec_fds[0], b->ptr, b->size - 1)) > 0; + b = chunkqueue_get_append_buffer(con->write_queue), buffer_prepare_copy(b, 4096)) { + + b->used = r; + b->ptr[b->used++] = '\0'; + } + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally"); + } + close(from_exec_fds[0]); + + break; + } + } +#else + + return -1; +#endif + + break; + } + case SSI_IF: { + const char *expr = NULL; + + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "expr")) { + expr = l[i+1]; + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + + if (!expr) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: ", + l[1], "expr missing"); + break; + } + + if ((!p->if_is_false) && + ((p->if_is_false_level == 0) || + (p->if_level < p->if_is_false_level))) { + switch (ssi_eval_expr(srv, con, p, expr)) { + case -1: + case 0: + p->if_is_false = 1; + p->if_is_false_level = p->if_level; + break; + case 1: + p->if_is_false = 0; + break; + } + } + + p->if_level++; + + break; + } + case SSI_ELSE: + p->if_level--; + + if (p->if_is_false) { + if ((p->if_level == p->if_is_false_level) && + (p->if_is_false_endif == 0)) { + p->if_is_false = 0; + } + } else { + p->if_is_false = 1; + + p->if_is_false_level = p->if_level; + } + p->if_level++; + + break; + case SSI_ELIF: { + const char *expr = NULL; + for (i = 2; i < n; i += 2) { + if (0 == strcmp(l[i], "expr")) { + expr = l[i+1]; + } else { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: unknow attribute for ", + l[1], l[i]); + } + } + + if (!expr) { + log_error_write(srv, __FILE__, __LINE__, "sss", + "ssi: ", + l[1], "expr missing"); + break; + } + + p->if_level--; + + if (p->if_level == p->if_is_false_level) { + if ((p->if_is_false) && + (p->if_is_false_endif == 0)) { + switch (ssi_eval_expr(srv, con, p, expr)) { + case -1: + case 0: + p->if_is_false = 1; + p->if_is_false_level = p->if_level; + break; + case 1: + p->if_is_false = 0; + break; + } + } else { + p->if_is_false = 1; + p->if_is_false_level = p->if_level; + p->if_is_false_endif = 1; + } + } + + p->if_level++; + + break; + } + case SSI_ENDIF: + p->if_level--; + + if (p->if_level == p->if_is_false_level) { + p->if_is_false = 0; + p->if_is_false_endif = 0; + } + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "ss", + "ssi: unknow ssi-command:", + l[1]); + break; + } + + return 0; + +} + +static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) { + stream s; +#ifdef HAVE_PCRE_H + int i, n; + +#define N 10 + int ovec[N * 3]; +#endif + + /* get a stream to the file */ + + array_reset(p->ssi_vars); + array_reset(p->ssi_cgi_env); + buffer_copy_string(p->timefmt, "%a, %d %b %Y %H:%M:%S %Z"); + p->sizefmt = 0; + build_ssi_cgi_vars(srv, con, p); + p->if_is_false = 0; + + if (-1 == stream_open(&s, con->physical.path)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "stream-open: ", con->physical.path); + return -1; + } + + + /** + * <!--#element attribute=value attribute=value ... --> + * + * config DONE + * errmsg -- missing + * sizefmt DONE + * timefmt DONE + * echo DONE + * var DONE + * encoding -- missing + * exec DONE + * cgi -- never + * cmd DONE + * fsize DONE + * file DONE + * virtual DONE + * flastmod DONE + * file DONE + * virtual DONE + * include DONE + * file DONE + * virtual DONE + * printenv DONE + * set DONE + * var DONE + * value DONE + * + * if DONE + * elif DONE + * else DONE + * endif DONE + * + * + * expressions + * AND, OR DONE + * comp DONE + * ${...} -- missing + * $... DONE + * '...' DONE + * ( ... ) DONE + * + * + * + * ** all DONE ** + * DATE_GMT + * The current date in Greenwich Mean Time. + * DATE_LOCAL + * The current date in the local time zone. + * DOCUMENT_NAME + * The filename (excluding directories) of the document requested by the user. + * DOCUMENT_URI + * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document. + * LAST_MODIFIED + * The last modification date of the document requested by the user. + * USER_NAME + * Contains the owner of the file which included it. + * + */ +#ifdef HAVE_PCRE_H + for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) { + const char **l; + /* take every think from last offset to current match pos */ + + if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i); + + pcre_get_substring_list(s.start, ovec, n, &l); + process_ssi_stmt(srv, con, p, l, n); + pcre_free_substring_list(l); + } + + switch(n) { + case PCRE_ERROR_NOMATCH: + /* copy everything/the rest */ + chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i); + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", + "execution error while matching: ", n); + break; + } +#endif + + + stream_close(&s); + + con->file_started = 1; + con->file_finished = 1; + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + + /* reset physical.path */ + buffer_reset(con->physical.path); + + return 0; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) { + PATCH(ssi_extension); + } + } + } + + return 0; +} + +static int mod_ssi_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(ssi_extension); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_ssi_physical_path) { + plugin_data *p = p_d; + size_t k, i; + + if (con->physical.path->used == 0) return HANDLER_GO_ON; + + mod_ssi_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_ssi_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + for (k = 0; k < p->conf.ssi_extension->used; k++) { + data_string *ds = (data_string *)p->conf.ssi_extension->data[k]; + + if (ds->value->used == 0) continue; + + if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) { + /* handle ssi-request */ + + if (mod_ssi_handle_request(srv, con, p)) { + /* on error */ + con->http_status = 500; + } + + return HANDLER_FINISHED; + } + } + + /* not found */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_ssi_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("ssi"); + + p->init = mod_ssi_init; + p->handle_physical_path = mod_ssi_physical_path; + p->set_defaults = mod_ssi_set_defaults; + p->cleanup = mod_ssi_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_ssi.h b/src/mod_ssi.h new file mode 100644 index 00000000..80f03ed8 --- /dev/null +++ b/src/mod_ssi.h @@ -0,0 +1,43 @@ +#ifndef _MOD_SSI_H_ +#define _MOD_SSI_H_ + +#include "base.h" +#include "buffer.h" +#include "array.h" + +#include "plugin.h" + +#ifdef HAVE_PCRE_H +#include <pcre.h> +#endif + +/* plugin config for all request/connections */ + +typedef struct { + array *ssi_extension; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + +#ifdef HAVE_PCRE_H + pcre *ssi_regex; +#endif + buffer *timefmt; + int sizefmt; + + buffer *stat_fn; + + array *ssi_vars; + array *ssi_cgi_env; + + int if_level, if_is_false_level, if_is_false, if_is_false_endif; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +int ssi_eval_expr(server *srv, connection *con, plugin_data *p, const char *expr); + +#endif diff --git a/src/mod_ssi_expr.c b/src/mod_ssi_expr.c new file mode 100644 index 00000000..98959abe --- /dev/null +++ b/src/mod_ssi_expr.c @@ -0,0 +1,324 @@ +#include <ctype.h> +#include <string.h> + +#include "buffer.h" +#include "log.h" +#include "mod_ssi.h" +#include "mod_ssi_expr.h" +#include "mod_ssi_exprparser.h" + +typedef struct { + const char *input; + size_t offset; + size_t size; + + int line_pos; + + int in_key; + int in_brace; + int in_cond; +} ssi_tokenizer_t; + +ssi_val_t *ssi_val_init() { + ssi_val_t *s; + + s = calloc(1, sizeof(*s)); + + return s; +} + +void ssi_val_free(ssi_val_t *s) { + if (s->str) buffer_free(s->str); + + free(s); +} + +int ssi_val_tobool(ssi_val_t *B) { + if (B->type == SSI_TYPE_STRING) { + return B->str->used > 1 ? 1 : 0; + } else { + return B->bo; + } +} + +static int ssi_expr_tokenizer(server *srv, connection *con, plugin_data *p, + ssi_tokenizer_t *t, int *token_id, buffer *token) { + int tid = 0; + size_t i; + + UNUSED(con); + + for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) { + char c = t->input[t->offset]; + data_string *ds; + + switch (c) { + case '=': + tid = TK_EQ; + + t->offset++; + t->line_pos++; + + buffer_copy_string(token, "(=)"); + + break; + case '>': + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + t->line_pos += 2; + + tid = TK_GE; + + buffer_copy_string(token, "(>=)"); + } else { + t->offset += 1; + t->line_pos += 1; + + tid = TK_GT; + + buffer_copy_string(token, "(>)"); + } + + break; + case '<': + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + t->line_pos += 2; + + tid = TK_LE; + + buffer_copy_string(token, "(<=)"); + } else { + t->offset += 1; + t->line_pos += 1; + + tid = TK_LT; + + buffer_copy_string(token, "(<)"); + } + + break; + + case '!': + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + t->line_pos += 2; + + tid = TK_NE; + + buffer_copy_string(token, "(!=)"); + } else { + t->offset += 1; + t->line_pos += 1; + + tid = TK_NOT; + + buffer_copy_string(token, "(!)"); + } + + break; + case '&': + if (t->input[t->offset + 1] == '&') { + t->offset += 2; + t->line_pos += 2; + + tid = TK_AND; + + buffer_copy_string(token, "(&&)"); + } else { + log_error_write(srv, __FILE__, __LINE__, "sds", + "pos:", t->line_pos, + "missing second &"); + return -1; + } + + break; + case '|': + if (t->input[t->offset + 1] == '|') { + t->offset += 2; + t->line_pos += 2; + + tid = TK_OR; + + buffer_copy_string(token, "(||)"); + } else { + log_error_write(srv, __FILE__, __LINE__, "sds", + "pos:", t->line_pos, + "missing second |"); + return -1; + } + + break; + case '\t': + case ' ': + t->offset++; + t->line_pos++; + break; + + case '\'': + /* search for the terminating " */ + for (i = 1; t->input[t->offset + i] && t->input[t->offset + i] != '\''; i++); + + if (t->input[t->offset + i]) { + tid = TK_VALUE; + + buffer_copy_string_len(token, t->input + t->offset + 1, i-1); + + t->offset += i + 1; + t->line_pos += i + 1; + } else { + /* ERROR */ + + log_error_write(srv, __FILE__, __LINE__, "sds", + "pos:", t->line_pos, + "missing closing quote"); + + return -1; + } + + break; + case '(': + t->offset++; + t->in_brace++; + + tid = TK_LPARAN; + + buffer_copy_string(token, "("); + break; + case ')': + t->offset++; + t->in_brace--; + + tid = TK_RPARAN; + + buffer_copy_string(token, ")"); + break; + case '$': + if (t->input[t->offset + 1] == '{') { + for (i = 2; t->input[t->offset + i] && t->input[t->offset + i] != '}'; i++); + + if (t->input[t->offset + i] != '}') { + log_error_write(srv, __FILE__, __LINE__, "sds", + "pos:", t->line_pos, + "missing closing quote"); + + return -1; + } + + buffer_copy_string_len(token, t->input + t->offset + 2, i-3); + } else { + for (i = 1; isalpha(t->input[t->offset + i]) || t->input[t->offset + i] == '_'; i++); + + buffer_copy_string_len(token, t->input + t->offset + 1, i-1); + } + + tid = TK_VALUE; + + if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, token->ptr))) { + buffer_copy_string_buffer(token, ds->value); + } else if (NULL != (ds = (data_string *)array_get_element(p->ssi_vars, token->ptr))) { + buffer_copy_string_buffer(token, ds->value); + } else { + buffer_copy_string(token, ""); + } + + t->offset += i; + t->line_pos += i; + + break; + default: + for (i = 0; isgraph(t->input[t->offset + i]); i++) { + char d = t->input[t->offset + i]; + switch(d) { + case ' ': + case '\t': + case ')': + case '(': + case '\'': + case '=': + case '!': + case '<': + case '>': + case '&': + case '|': + break; + } + } + + tid = TK_VALUE; + + buffer_copy_string_len(token, t->input + t->offset, i); + + t->offset += i; + t->line_pos += i; + + break; + } + } + + if (tid) { + *token_id = tid; + + return 1; + } else if (t->offset < t->size) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "pos:", t->line_pos, + "foobar"); + } + return 0; +} + +int ssi_eval_expr(server *srv, connection *con, plugin_data *p, const char *expr) { + ssi_tokenizer_t t; + void *pParser; + int token_id; + buffer *token; + ssi_ctx_t context; + int ret; + + t.input = expr; + t.offset = 0; + t.size = strlen(expr); + t.line_pos = 1; + + t.in_key = 1; + t.in_brace = 0; + t.in_cond = 0; + + context.ok = 1; + context.srv = srv; + + /* default context */ + + pParser = ssiexprparserAlloc( malloc ); + token = buffer_init(); + while((1 == (ret = ssi_expr_tokenizer(srv, con, p, &t, &token_id, token))) && context.ok) { + ssiexprparser(pParser, token_id, token, &context); + + token = buffer_init(); + } + ssiexprparser(pParser, 0, token, &context); + ssiexprparserFree(pParser, free ); + + buffer_free(token); + + if (ret == -1) { + log_error_write(srv, __FILE__, __LINE__, "s", + "expr parser failed"); + return -1; + } + + if (context.ok == 0) { + log_error_write(srv, __FILE__, __LINE__, "sds", + "pos:", t.line_pos, + "parser failed somehow near here"); + return -1; + } +#if 0 + log_error_write(srv, __FILE__, __LINE__, "ssd", + "expr: ", + expr, + context.val.bo); +#endif + return context.val.bo; +} diff --git a/src/mod_ssi_expr.h b/src/mod_ssi_expr.h new file mode 100644 index 00000000..b484f788 --- /dev/null +++ b/src/mod_ssi_expr.h @@ -0,0 +1,31 @@ +#ifndef _MOD_SSI_EXPR_H_ +#define _MOD_SSI_EXPR_H_ + +#include "buffer.h" + +typedef struct { + enum { SSI_TYPE_UNSET, SSI_TYPE_BOOL, SSI_TYPE_STRING } type; + + buffer *str; + int bo; +} ssi_val_t; + +typedef struct { + int ok; + + ssi_val_t val; + + void *srv; +} ssi_ctx_t; + +typedef enum { SSI_COND_UNSET, SSI_COND_LE, SSI_COND_GE, SSI_COND_EQ, SSI_COND_NE, SSI_COND_LT, SSI_COND_GT } ssi_expr_cond; + +void *ssiexprparserAlloc(void *(*mallocProc)(size_t)); +void ssiexprparserFree(void *p, void (*freeProc)(void*)); +void ssiexprparser(void *yyp, int yymajor, buffer *yyminor, ssi_ctx_t *ctx); + +int ssi_val_tobool(ssi_val_t *B); +ssi_val_t *ssi_val_init(); +void ssi_val_free(ssi_val_t *s); + +#endif diff --git a/src/mod_ssi_exprparser.h b/src/mod_ssi_exprparser.h new file mode 100644 index 00000000..eb55ea5f --- /dev/null +++ b/src/mod_ssi_exprparser.h @@ -0,0 +1,12 @@ +#define TK_AND 1 +#define TK_OR 2 +#define TK_EQ 3 +#define TK_NE 4 +#define TK_GT 5 +#define TK_GE 6 +#define TK_LT 7 +#define TK_LE 8 +#define TK_NOT 9 +#define TK_LPARAN 10 +#define TK_RPARAN 11 +#define TK_VALUE 12 diff --git a/src/mod_ssi_exprparser.y b/src/mod_ssi_exprparser.y new file mode 100644 index 00000000..1bf229b7 --- /dev/null +++ b/src/mod_ssi_exprparser.y @@ -0,0 +1,119 @@ +%token_prefix TK_ +%token_type {buffer *} +%extra_argument {ssi_ctx_t *ctx} +%name ssiexprparser + +%include { +#include <assert.h> +#include <string.h> +#include "config.h" +#include "mod_ssi_expr.h" +#include "buffer.h" +} + +%parse_failure { + ctx->ok = 0; +} + +%type expr { ssi_val_t * } +%type value { buffer * } +%type exprline { ssi_val_t * } +%type cond { int } +%token_destructor { buffer_free($$); } + +%left AND. +%left OR. +%nonassoc EQ NE GT GE LT LE. +%right NOT. + +input ::= exprline(B). { + ctx->val.bo = ssi_val_tobool(B); + ctx->val.type = SSI_TYPE_BOOL; + + ssi_val_free(B); +} + +exprline(A) ::= expr(B) cond(C) expr(D). { + int cmp; + + if (B->type == SSI_TYPE_STRING && + D->type == SSI_TYPE_STRING) { + cmp = strcmp(B->str->ptr, D->str->ptr); + } else { + cmp = ssi_val_tobool(B) - ssi_val_tobool(D); + } + + A = B; + + switch(C) { + case SSI_COND_EQ: A->bo = (cmp == 0) ? 1 : 0; break; + case SSI_COND_NE: A->bo = (cmp != 0) ? 1 : 0; break; + case SSI_COND_GE: A->bo = (cmp >= 0) ? 1 : 0; break; + case SSI_COND_GT: A->bo = (cmp > 0) ? 1 : 0; break; + case SSI_COND_LE: A->bo = (cmp <= 0) ? 1 : 0; break; + case SSI_COND_LT: A->bo = (cmp < 0) ? 1 : 0; break; + } + + A->type = SSI_TYPE_BOOL; + + ssi_val_free(D); +} +exprline(A) ::= expr(B). { + A = B; +} +expr(A) ::= expr(B) AND expr(C). { + int e; + + e = ssi_val_tobool(B) && ssi_val_tobool(C); + + A = B; + A->bo = e; + A->type = SSI_TYPE_BOOL; + ssi_val_free(C); +} + +expr(A) ::= expr(B) OR expr(C). { + int e; + + e = ssi_val_tobool(B) || ssi_val_tobool(C); + + A = B; + A->bo = e; + A->type = SSI_TYPE_BOOL; + ssi_val_free(C); +} + +expr(A) ::= NOT expr(B). { + int e; + + e = !ssi_val_tobool(B); + + A = B; + A->bo = e; + A->type = SSI_TYPE_BOOL; +} +expr(A) ::= LPARAN exprline(B) RPARAN. { + A = B; +} + +expr(A) ::= value(B). { + A = ssi_val_init(); + A->str = B; + A->type = SSI_TYPE_STRING; +} + +value(A) ::= VALUE(B). { + A = buffer_init_string(B->ptr); +} + +value(A) ::= value(B) VALUE(C). { + A = B; + buffer_append_string_buffer(A, C); +} + +cond(A) ::= EQ. { A = SSI_COND_EQ; } +cond(A) ::= NE. { A = SSI_COND_NE; } +cond(A) ::= LE. { A = SSI_COND_LE; } +cond(A) ::= GE. { A = SSI_COND_GE; } +cond(A) ::= LT. { A = SSI_COND_LT; } +cond(A) ::= GT. { A = SSI_COND_GT; } diff --git a/src/mod_status.c b/src/mod_status.c new file mode 100644 index 00000000..ed51f329 --- /dev/null +++ b/src/mod_status.c @@ -0,0 +1,653 @@ +#define _GNU_SOURCE +#include <sys/types.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "server.h" +#include "connections.h" +#include "response.h" +#include "connections.h" +#include "log.h" + +#include "plugin.h" + +#include "inet_ntop_cache.h" + +typedef struct { + buffer *config_url; + buffer *status_url; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + double traffic_out; + double requests; + + double mod_5s_traffic_out[5]; + double mod_5s_requests[5]; + size_t mod_5s_ndx; + + double rel_traffic_out; + double rel_requests; + + double abs_traffic_out; + double abs_requests; + + double bytes_written; + + buffer *module_list; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +INIT_FUNC(mod_status_init) { + plugin_data *p; + size_t i; + + p = calloc(1, sizeof(*p)); + + p->traffic_out = p->requests = 0; + p->rel_traffic_out = p->rel_requests = 0; + p->abs_traffic_out = p->abs_requests = 0; + p->bytes_written = 0; + p->module_list = buffer_init(); + + for (i = 0; i < 5; i++) { + p->mod_5s_traffic_out[i] = p->mod_5s_requests[i] = 0; + } + + return p; +} + +FREE_FUNC(mod_status_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + buffer_free(p->module_list); + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + buffer_free(s->status_url); + buffer_free(s->config_url); + + free(s); + } + free(p->config_storage); + } + + + free(p); + + return HANDLER_GO_ON; +} + +SETDEFAULTS_FUNC(mod_status_set_defaults) { + plugin_data *p = p_d; + size_t i; + + config_values_t cv[] = { + { "status.status-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { "status.config-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->config_url = buffer_init(); + s->status_url = buffer_init(); + + cv[0].destination = s->status_url; + cv[1].destination = s->config_url; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + + + +static int mod_status_row_append(buffer *b, const char *key, const char *value) { + BUFFER_APPEND_STRING_CONST(b, " <tr>\n"); + BUFFER_APPEND_STRING_CONST(b, " <td><b>"); + buffer_append_string(b, key); + BUFFER_APPEND_STRING_CONST(b, "</b></td>\n"); + BUFFER_APPEND_STRING_CONST(b, " <td>"); + buffer_append_string(b, value); + BUFFER_APPEND_STRING_CONST(b, "</td>\n"); + BUFFER_APPEND_STRING_CONST(b, " </tr>\n"); + + return 0; +} + +static int mod_status_header_append(buffer *b, const char *key) { + BUFFER_APPEND_STRING_CONST(b, " <tr>\n"); + BUFFER_APPEND_STRING_CONST(b, " <th colspan=\"2\">"); + buffer_append_string(b, key); + BUFFER_APPEND_STRING_CONST(b, "</th>\n"); + BUFFER_APPEND_STRING_CONST(b, " </tr>\n"); + + return 0; +} + +static handler_t mod_status_handle_server_status(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + buffer *b; + size_t j; + double avg; + char multiplier = '\0'; + char buf[32]; + time_t ts; + + int days, hours, mins, seconds; + + b = chunkqueue_get_append_buffer(con->write_queue); + + BUFFER_COPY_STRING_CONST(b, + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + " <head>\n" + " <title>Status</title>\n"); + + BUFFER_APPEND_STRING_CONST(b, + " <style type=\"text/css\">\n" + "th.status { background-color: black; color: white; }\n" + "table.status { border: black solid thin; }\n" + "td.int { background-color: #f0f0f0; text-align: right }\n" + "td.string { background-color: #f0f0f0; text-align: left }\n" + " </style>\n"); + + BUFFER_APPEND_STRING_CONST(b, + " </head>\n" + " <body>\n"); + + + + /* connection listing */ + BUFFER_APPEND_STRING_CONST(b, "<h1>Server-Status</h1>"); + + BUFFER_APPEND_STRING_CONST(b, "<table class=\"status\">"); + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Hostname</td><td class=\"string\">"); + buffer_append_string_buffer(b, con->uri.authority); + BUFFER_APPEND_STRING_CONST(b, " ("); + buffer_append_string_buffer(b, con->server_name); + BUFFER_APPEND_STRING_CONST(b, ")</td></tr>\n"); + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Uptime</td><td class=\"string\">"); + + ts = srv->cur_ts - srv->startup_ts; + + days = ts / (60 * 60 * 24); + ts %= (60 * 60 * 24); + + hours = ts / (60 * 60); + ts %= (60 * 60); + + mins = ts / (60); + ts %= (60); + + seconds = ts; + + if (days) { + buffer_append_long(b, days); + BUFFER_APPEND_STRING_CONST(b, " days "); + } + + if (hours) { + buffer_append_long(b, hours); + BUFFER_APPEND_STRING_CONST(b, " hours "); + } + + if (mins) { + buffer_append_long(b, mins); + BUFFER_APPEND_STRING_CONST(b, " min "); + } + + buffer_append_long(b, seconds); + BUFFER_APPEND_STRING_CONST(b, " s"); + + BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n"); + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Started at</td><td class=\"string\">"); + + ts = srv->startup_ts; + + strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", localtime(&ts)); + buffer_append_string(b, buf); + BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n"); + + + BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"); + + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">"); + avg = p->abs_requests; + multiplier = '\0'; + if (avg > 1000) { avg /= 1000; multiplier = 'k'; } + if (avg > 1000) { avg /= 1000; multiplier = 'M'; } + if (avg > 1000) { avg /= 1000; multiplier = 'G'; } + if (avg > 1000) { avg /= 1000; multiplier = 'T'; } + if (avg > 1000) { avg /= 1000; multiplier = 'P'; } + if (avg > 1000) { avg /= 1000; multiplier = 'E'; } + if (avg > 1000) { avg /= 1000; multiplier = 'Z'; } + if (avg > 1000) { avg /= 1000; multiplier = 'Y'; } + buffer_append_long(b, avg); + BUFFER_APPEND_STRING_CONST(b, " "); + if (multiplier) buffer_append_string_len(b, &multiplier, 1); + BUFFER_APPEND_STRING_CONST(b, "req</td></tr>\n"); + + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">"); + avg = p->abs_traffic_out; + multiplier = '\0'; + + if (avg > 1024) { avg /= 1024; multiplier = 'k'; } + if (avg > 1024) { avg /= 1024; multiplier = 'M'; } + if (avg > 1024) { avg /= 1024; multiplier = 'G'; } + if (avg > 1024) { avg /= 1024; multiplier = 'T'; } + if (avg > 1024) { avg /= 1024; multiplier = 'P'; } + if (avg > 1024) { avg /= 1024; multiplier = 'E'; } + if (avg > 1024) { avg /= 1024; multiplier = 'Z'; } + if (avg > 1024) { avg /= 1024; multiplier = 'Y'; } + + buffer_append_long(b, avg); + BUFFER_APPEND_STRING_CONST(b, " "); + if (multiplier) buffer_append_string_len(b, &multiplier, 1); + BUFFER_APPEND_STRING_CONST(b, "byte</td></tr>\n"); + + + BUFFER_APPEND_STRING_CONST(b, "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"); + for (j = 0, avg = 0; j < 5; j++) { + avg += p->mod_5s_requests[j]; + } + + avg /= 5; + + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Requests</td><td class=\"string\">"); + multiplier = '\0'; + + if (avg > 1024) { avg /= 1024; multiplier = 'k'; } + if (avg > 1024) { avg /= 1024; multiplier = 'M'; } + if (avg > 1024) { avg /= 1024; multiplier = 'G'; } + if (avg > 1024) { avg /= 1024; multiplier = 'T'; } + if (avg > 1024) { avg /= 1024; multiplier = 'P'; } + if (avg > 1024) { avg /= 1024; multiplier = 'E'; } + if (avg > 1024) { avg /= 1024; multiplier = 'Z'; } + if (avg > 1024) { avg /= 1024; multiplier = 'Y'; } + + buffer_append_long(b, avg); + BUFFER_APPEND_STRING_CONST(b, " "); + if (multiplier) buffer_append_string_len(b, &multiplier, 1); + + BUFFER_APPEND_STRING_CONST(b, "req/s</td></tr>\n"); + + for (j = 0, avg = 0; j < 5; j++) { + avg += p->mod_5s_traffic_out[j]; + } + + avg /= 5; + + BUFFER_APPEND_STRING_CONST(b, "<tr><td>Traffic</td><td class=\"string\">"); + multiplier = '\0'; + + if (avg > 1024) { avg /= 1024; multiplier = 'k'; } + if (avg > 1024) { avg /= 1024; multiplier = 'M'; } + if (avg > 1024) { avg /= 1024; multiplier = 'G'; } + if (avg > 1024) { avg /= 1024; multiplier = 'T'; } + if (avg > 1024) { avg /= 1024; multiplier = 'P'; } + if (avg > 1024) { avg /= 1024; multiplier = 'E'; } + if (avg > 1024) { avg /= 1024; multiplier = 'Z'; } + if (avg > 1024) { avg /= 1024; multiplier = 'Y'; } + + buffer_append_long(b, avg); + BUFFER_APPEND_STRING_CONST(b, " "); + if (multiplier) buffer_append_string_len(b, &multiplier, 1); + BUFFER_APPEND_STRING_CONST(b, "byte/s</td></tr>\n"); + + BUFFER_APPEND_STRING_CONST(b, "</table>\n"); + + + BUFFER_APPEND_STRING_CONST(b, "<hr />\n<pre><b>legend</b>\n"); + BUFFER_APPEND_STRING_CONST(b, ". = connect, C = close, E = hard error\n"); + BUFFER_APPEND_STRING_CONST(b, "r = read, R = read-POST, W = write, h = handle-request\n"); + BUFFER_APPEND_STRING_CONST(b, "q = request-start, Q = request-end\n"); + BUFFER_APPEND_STRING_CONST(b, "s = response-start, S = response-end\n"); + + BUFFER_APPEND_STRING_CONST(b, "<b>"); + buffer_append_long(b, srv->conns->used); + BUFFER_APPEND_STRING_CONST(b, " connections</b>\n"); + + for (j = 0; j < srv->conns->used; j++) { + connection *c = srv->conns->ptr[j]; + const char *state = connection_get_short_state(c->state); + + buffer_append_string_len(b, state, 1); + + if (((j + 1) % 50) == 0) { + BUFFER_APPEND_STRING_CONST(b, "\n"); + } + } + + BUFFER_APPEND_STRING_CONST(b, "\n</pre><hr />\n<h2>Connections</h2>\n"); + + BUFFER_APPEND_STRING_CONST(b, "<table class=\"status\">\n"); + BUFFER_APPEND_STRING_CONST(b, "<tr>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Client IP</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Read</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Written</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">State</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Time</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">Host</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">URI</th>"); + BUFFER_APPEND_STRING_CONST(b, "<th class=\"status\">File</th>"); + BUFFER_APPEND_STRING_CONST(b, "</tr>\n"); + + for (j = 0; j < srv->conns->used; j++) { + connection *c = srv->conns->ptr[j]; + + BUFFER_APPEND_STRING_CONST(b, "<tr><td class=\"string\">"); + + buffer_append_string(b, inet_ntop_cache_get_ip(srv, &(c->dst_addr))); + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">"); + + if (con->request.content_length) { + buffer_append_long(b, c->request.content->used); + BUFFER_APPEND_STRING_CONST(b, "/"); + buffer_append_long(b, c->request.content_length); + } else { + BUFFER_APPEND_STRING_CONST(b, "0/0"); + } + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">"); + + buffer_append_off_t(b, chunkqueue_written(c->write_queue)); + BUFFER_APPEND_STRING_CONST(b, "/"); + buffer_append_off_t(b, chunkqueue_length(c->write_queue)); + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); + + buffer_append_string(b, connection_get_state(c->state)); + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"int\">"); + + buffer_append_long(b, srv->cur_ts - c->request_start); + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); + + buffer_append_string_buffer(b, c->server_name); + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); + + buffer_append_string_html_encoded(b, c->uri.path->ptr); + + BUFFER_APPEND_STRING_CONST(b, "</td><td class=\"string\">"); + + buffer_append_string_buffer(b, c->physical.path); + + BUFFER_APPEND_STRING_CONST(b, "</td></tr>\n"); + } + + + BUFFER_APPEND_STRING_CONST(b, + "</table>\n"); + + + BUFFER_APPEND_STRING_CONST(b, + " </body>\n" + "</html>\n" + ); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + + con->http_status = 200; + con->file_finished = 1; + + return HANDLER_FINISHED; +} + +static handler_t mod_status_handle_server_config(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + buffer *b, *m = p->module_list; + size_t i; + + struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] = + { + /* - poll is most reliable + * - select works everywhere + * - linux-* are experimental + */ +#ifdef USE_POLL + { FDEVENT_HANDLER_POLL, "poll" }, +#endif +#ifdef USE_SELECT + { FDEVENT_HANDLER_SELECT, "select" }, +#endif +#ifdef USE_LINUX_EPOLL + { FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" }, +#endif +#ifdef USE_LINUX_SIGIO + { FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig" }, +#endif +#ifdef USE_SOLARIS_DEVPOLL + { FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" }, +#endif +#ifdef USE_FREEBSD_KQUEUE + { FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" }, +#endif + { FDEVENT_HANDLER_UNSET, NULL } + }; + + b = chunkqueue_get_append_buffer(con->write_queue); + + BUFFER_COPY_STRING_CONST(b, + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + " <head>\n" + " <title>Status</title>\n" + " </head>\n" + " <body>\n" + " <h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" + " <table border=\"1\">\n"); + + mod_status_header_append(b, "Server-Features"); +#ifdef HAVE_PCRE_H + mod_status_row_append(b, "Rewrite Engine", "enabled"); +#else + mod_status_row_append(b, "Rewrite Engine", "disabled - pcre missing"); +#endif +#ifdef HAVE_ZLIB_H + mod_status_row_append(b, "On-the-Fly Output Compression", "enabled"); +#else + mod_status_row_append(b, "On-the-Fly Output Compression", "disabled - zlib missing"); +#endif + mod_status_header_append(b, "Network Engine"); + + for (i = 0; event_handlers[i].name; i++) { + if (event_handlers[i].et == srv->event_handler) { + mod_status_row_append(b, "fd-Event-Handler", event_handlers[i].name); + break; + } + } + + mod_status_header_append(b, "Config-File-Settings"); + mod_status_row_append(b, "Directory Listings", con->conf.dir_listing ? "enabled" : "disabled"); + + for (i = 0; i < srv->plugins.used; i++) { + plugin **ps = srv->plugins.ptr; + + plugin *pl = ps[i]; + + if (i == 0) { + buffer_copy_string_buffer(m, pl->name); + } else { + BUFFER_APPEND_STRING_CONST(m, "<br />"); + buffer_append_string_buffer(m, pl->name); + } + } + + mod_status_row_append(b, "Loaded Modules", m->ptr); + + BUFFER_APPEND_STRING_CONST(b, " </table>\n"); + + BUFFER_APPEND_STRING_CONST(b, + " </body>\n" + "</html>\n" + ); + + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + + con->http_status = 200; + con->file_finished = 1; + + return HANDLER_FINISHED; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_skeleton_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.status-url"))) { + PATCH(status_url); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("status.config-url"))) { + PATCH(config_url); + } + } + } + + return 0; +} + +static int mod_skeleton_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(status_url); + PATCH(config_url); + + return 0; +} +#undef PATCH + +static handler_t mod_status_handler(server *srv, connection *con, void *p_d) { + plugin_data *p = p_d; + size_t i; + + mod_skeleton_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_skeleton_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + if (!buffer_is_empty(p->conf.status_url) && + buffer_is_equal(p->conf.status_url, con->uri.path)) { + return mod_status_handle_server_status(srv, con, p_d); + } else if (!buffer_is_empty(p->conf.config_url) && + buffer_is_equal(p->conf.config_url, con->uri.path)) { + return mod_status_handle_server_config(srv, con, p_d); + } + + return HANDLER_GO_ON; +} + +TRIGGER_FUNC(mod_status_trigger) { + plugin_data *p = p_d; + size_t i; + + /* check all connections */ + for (i = 0; i < srv->conns->used; i++) { + connection *c = srv->conns->ptr[i]; + + p->bytes_written += c->bytes_written_cur_second; + } + + /* a sliding average */ + p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written; + p->mod_5s_requests [p->mod_5s_ndx] = p->requests; + + p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5; + + p->abs_traffic_out += p->bytes_written; + p->rel_traffic_out += p->bytes_written; + + p->bytes_written = 0; + + /* reset storage - second */ + p->traffic_out = 0; + p->requests = 0; + + return HANDLER_GO_ON; +} + +REQUESTDONE_FUNC(mod_status_account) { + plugin_data *p = p_d; + + UNUSED(srv); + + p->requests++; + p->rel_requests++; + p->abs_requests++; + + p->bytes_written += con->bytes_written_cur_second; + + return HANDLER_GO_ON; +} + +int mod_status_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("status"); + + p->init = mod_status_init; + p->cleanup = mod_status_free; + p->set_defaults= mod_status_set_defaults; + + p->handle_uri_clean = mod_status_handler; + p->handle_trigger = mod_status_trigger; + p->handle_request_done = mod_status_account; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_userdir.c b/src/mod_userdir.c new file mode 100644 index 00000000..d098fbc2 --- /dev/null +++ b/src/mod_userdir.c @@ -0,0 +1,248 @@ +#include <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "response.h" + +#include "plugin.h" + +#include "config.h" + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +/* plugin config for all request/connections */ +typedef struct { + array *exclude_user; + array *include_user; + buffer *path; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + buffer *username; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_userdir_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + p->username = buffer_init(); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_userdir_free) { + plugin_data *p = p_d; + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + array_free(s->include_user); + array_free(s->exclude_user); + buffer_free(s->path); + + free(s); + } + free(p->config_storage); + } + + buffer_free(p->username); + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_userdir_set_defaults) { + plugin_data *p = p_d; + size_t i; + + config_values_t cv[] = { + { "userdir.path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "userdir.exclude-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "userdir.include-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->exclude_user = array_init(); + s->include_user = array_init(); + s->path = buffer_init(); + + cv[0].destination = s->path; + cv[1].destination = s->exclude_user; + cv[2].destination = s->include_user; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_userdir_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.path"))) { + PATCH(path); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.exclude-user"))) { + PATCH(exclude_user); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.include-user"))) { + PATCH(include_user); + } + } + } + + return 0; +} + +static int mod_userdir_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(path); + PATCH(exclude_user); + PATCH(include_user); + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_userdir_docroot_handler) { + plugin_data *p = p_d; + int uri_len; + size_t k; + char *rel_url; + struct passwd *pwd; + size_t i; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; +#ifdef HAVE_PWD_H + mod_userdir_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_userdir_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + uri_len = con->uri.path->used - 1; + + /* /~user/foo.html -> /home/user/public_html/foo.html */ + + if (con->uri.path->ptr[0] != '/' || + con->uri.path->ptr[1] != '~') return HANDLER_GO_ON; + + if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) { + /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */ + http_response_redirect_to_directory(srv, con); + + return HANDLER_FINISHED; + } + buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2)); + + if (NULL == (pwd = getpwnam(p->username->ptr))) { + /* user not found */ + return HANDLER_GO_ON; + } + + for (k = 0; k < p->conf.exclude_user->used; k++) { + data_string *ds = (data_string *)p->conf.exclude_user->data[k]; + + if (buffer_is_equal(ds->value, p->username)) { + /* user in exclude list */ + return HANDLER_GO_ON; + } + } + + if (p->conf.include_user->used) { + int found_user = 0; + for (k = 0; k < p->conf.include_user->used; k++) { + data_string *ds = (data_string *)p->conf.include_user->data[k]; + + if (buffer_is_equal(ds->value, p->username)) { + /* user in exclude list */ + found_user = 1; + break; + } + } + + if (!found_user) return HANDLER_GO_ON; + } + + buffer_copy_string(con->physical.path, pwd->pw_dir); + BUFFER_APPEND_SLASH(con->physical.path); + buffer_append_string_buffer(con->physical.path, p->conf.path); /* skip the / */ + BUFFER_APPEND_SLASH(con->physical.path); + buffer_append_string(con->physical.path, rel_url + 1); /* skip the / */ + + return HANDLER_GO_ON; +#else + return HANDLER_ERROR; +#endif +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_userdir_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("userdir"); + + p->init = mod_userdir_init; + p->handle_docroot = mod_userdir_docroot_handler; + p->set_defaults = mod_userdir_set_defaults; + p->cleanup = mod_userdir_free; + + p->data = NULL; + + return 0; +} diff --git a/src/mod_usertrack.c b/src/mod_usertrack.c new file mode 100644 index 00000000..7b53fddf --- /dev/null +++ b/src/mod_usertrack.c @@ -0,0 +1,247 @@ +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#ifdef USE_OPENSSL +# include <openssl/md5.h> +#else +# include "md5_global.h" +# include "md5.h" +#endif + +/* plugin config for all request/connections */ + +typedef struct { + buffer *cookie_name; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_usertrack_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +/* detroy the plugin data */ +FREE_FUNC(mod_usertrack_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + buffer_free(s->cookie_name); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_usertrack_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "usertrack.cookiename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = malloc(srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s; + + s = malloc(sizeof(plugin_config)); + s->cookie_name = buffer_init(); + + cv[0].destination = s->cookie_name; + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { + return HANDLER_ERROR; + } + + if (s->cookie_name->used == 0) { + buffer_copy_string(s->cookie_name, "TRACKID"); + } else { + size_t j; + for (j = 0; j < s->cookie_name->used - 1; j++) { + char c = s->cookie_name->ptr[j] | 32; + if (c < 'a' || c > 'z') { + log_error_write(srv, __FILE__, __LINE__, "sb", + "invalid character in usertrack.cookiename:", + s->cookie_name); + + return HANDLER_ERROR; + } + } + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_usertrack_patch_connection(server *srv, connection *con, plugin_data *p, const char *stage, size_t stage_len) { + size_t i, j; + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + plugin_config *s = p->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, stage, stage_len)) continue; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookiename"))) { + PATCH(cookie_name); + } + } + } + + return 0; +} + +static int mod_usertrack_setup_connection(server *srv, connection *con, plugin_data *p) { + plugin_config *s = p->config_storage[0]; + UNUSED(srv); + UNUSED(con); + + PATCH(cookie_name); + + return 0; +} +#undef PATCH + + + +URIHANDLER_FUNC(mod_usertrack_uri_handler) { + plugin_data *p = p_d; + data_string *ds; + unsigned char h[16]; + MD5_CTX Md5Ctx; + char hh[32]; + size_t i; + + if (con->uri.path->used == 0) return HANDLER_GO_ON; + + mod_usertrack_setup_connection(srv, con, p); + for (i = 0; i < srv->config_patches->used; i++) { + buffer *patch = srv->config_patches->ptr[i]; + + mod_usertrack_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); + } + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) { + char *g; + /* we have a cookie, does it contain a valid name ? */ + + /* parse the cookie + * + * check for cookiename + (WS | '=') + * + */ + + if (NULL != (g = strstr(ds->value->ptr, p->conf.cookie_name->ptr))) { + char *nc; + + /* skip WS */ + for (nc = g + p->conf.cookie_name->used-1; *nc == ' ' || *nc == '\t'; nc++); + + if (*nc == '=') { + /* ok, found the key of our own cookie */ + + if (strlen(nc) > 32) { + /* i'm lazy */ + return HANDLER_GO_ON; + } + } + } + } + + /* set a cookie */ + if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { + ds = data_response_init(); + } + buffer_copy_string(ds->key, "Set-Cookie"); + buffer_copy_string_buffer(ds->value, p->conf.cookie_name); + buffer_append_string(ds->value, "="); + + + /* taken from mod_auth.c */ + + /* generate shared-secret */ + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)con->uri.path->ptr, con->uri.path->used - 1); + MD5_Update(&Md5Ctx, (unsigned char *)"+", 1); + + /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */ + ltostr(hh, srv->cur_ts); + MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); + ltostr(hh, rand()); + MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); + + MD5_Final(h, &Md5Ctx); + + buffer_append_string_hex(ds->value, (char *)h, 16); + buffer_append_string(ds->value, "; path=/"); + + array_insert_unique(con->response.headers, (data_unset *)ds); + + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_usertrack_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("usertrack"); + + p->init = mod_usertrack_init; + p->handle_uri_clean = mod_usertrack_uri_handler; + p->set_defaults = mod_usertrack_set_defaults; + p->cleanup = mod_usertrack_free; + + p->data = NULL; + + return 0; +} diff --git a/src/network.c b/src/network.c new file mode 100644 index 00000000..bcf289fd --- /dev/null +++ b/src/network.c @@ -0,0 +1,517 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" +#include "connections.h" +#include "plugin.h" +#include "joblist.h" + +#include "network_backends.h" +#include "sys-mmap.h" +#include "sys-socket.h" + +#ifdef USE_OPENSSL +# include <openssl/ssl.h> +# include <openssl/err.h> +# include <openssl/rand.h> +#endif + +handler_t network_server_handle_fdevent(void *s, void *context, int revents) { + server *srv = (server *)s; + server_socket *srv_socket = (server_socket *)context; + connection *con; + + UNUSED(context); + + if (revents != FDEVENT_IN) { + log_error_write(srv, __FILE__, __LINE__, "sdd", + "strange event for server socket", + srv_socket->fd, + revents); + return -1; + } + + while (NULL != (con = connection_accept(srv, srv_socket))) { + handler_t r; + + connection_state_machine(srv, con); + + switch(r = plugins_call_handle_joblist(srv, con)) { + case HANDLER_FINISHED: + case HANDLER_GO_ON: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "d", r); + break; + } + } + return HANDLER_GO_ON; +} + +int network_server_init(server *srv, buffer *host_token, specific_config *s) { + int val; + socklen_t addr_len; + server_socket *srv_socket; + char *sp; + unsigned int port = 0; + const char *host; + buffer *b; + +#ifdef SO_ACCEPTFILTER + struct accept_filter_arg afa; +#endif + +#ifdef __WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD( 2, 2 ); + + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* Tell the user that we could not find a usable */ + /* WinSock DLL. */ + return -1; + } +#endif + + srv_socket = calloc(1, sizeof(*srv_socket)); + srv_socket->fd = -1; + + srv_socket->srv_token = buffer_init(); + buffer_copy_string_buffer(srv_socket->srv_token, host_token); + + b = buffer_init(); + buffer_copy_string_buffer(b, host_token); + + /* ipv4:port + * [ipv6]:port + */ + if (NULL == (sp = strrchr(b->ptr, ':'))) { + log_error_write(srv, __FILE__, __LINE__, "sb", "value of $SERVER[\"socket\"] has to be \"ip:port\".", b); + + return -1; + } + + host = b->ptr; + + /* check for [ and ] */ + if (b->ptr[0] == '[' && *(sp-1) == ']') { + *(sp-1) = '\0'; + host++; + + s->use_ipv6 = 1; + } + + *(sp++) = '\0'; + + port = strtol(sp, NULL, 10); + + if (port == 0 || port > 65535) { + log_error_write(srv, __FILE__, __LINE__, "sd", "port out of range:", port); + + return -1; + } + + if (*host == '\0') host = NULL; + +#ifdef HAVE_IPV6 + if (s->use_ipv6) { + srv_socket->addr.plain.sa_family = AF_INET6; + + if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno)); + return -1; + } + srv_socket->use_ipv6 = 1; + } +#endif + + if (srv_socket->fd == -1) { + srv_socket->addr.plain.sa_family = AF_INET; + if (-1 == (srv_socket->fd = socket(srv_socket->addr.plain.sa_family, SOCK_STREAM, IPPROTO_TCP))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno)); + return -1; + } + } + + /* */ + srv->cur_fds = srv_socket->fd; + + val = 1; + if (setsockopt(srv_socket->fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + log_error_write(srv, __FILE__, __LINE__, "ss", "socketsockopt failed:", strerror(errno)); + return -1; + } + + if (-1 == fdevent_fcntl_set(srv->ev, srv_socket->fd)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed:", strerror(errno)); + return -1; + } + + switch(srv_socket->addr.plain.sa_family) { +#ifdef HAVE_IPV6 + case AF_INET6: + memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in6)); + srv_socket->addr.ipv6.sin6_family = AF_INET6; + if (host == NULL) { + srv_socket->addr.ipv6.sin6_addr = in6addr_any; + } else { + struct addrinfo hints, *res; + int r; + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (0 != (r = getaddrinfo(host, NULL, &hints, &res))) { + log_error_write(srv, __FILE__, __LINE__, + "sssss", "getaddrinfo failed: ", + gai_strerror(r), "'", host, "'"); + + return -1; + } + + memcpy(&(srv_socket->addr), res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + } + srv_socket->addr.ipv6.sin6_port = htons(port); + addr_len = sizeof(struct sockaddr_in6); + break; +#endif + case AF_INET: + memset(&srv_socket->addr, 0, sizeof(struct sockaddr_in)); + srv_socket->addr.ipv4.sin_family = AF_INET; + if (host == NULL) { + srv_socket->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + struct hostent *he; + if (NULL == (he = gethostbyname(host))) { + log_error_write(srv, __FILE__, __LINE__, + "sss", "gethostbyname failed: ", + hstrerror(h_errno), host); + return -1; + } + + if (he->h_addrtype != AF_INET) { + log_error_write(srv, __FILE__, __LINE__, "sd", "addr-type != AF_INET: ", he->h_addrtype); + return -1; + } + + if (he->h_length != sizeof(struct in_addr)) { + log_error_write(srv, __FILE__, __LINE__, "sd", "addr-length != sizeof(in_addr): ", he->h_length); + return -1; + } + + memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr), he->h_addr_list[0], he->h_length); + } + srv_socket->addr.ipv4.sin_port = htons(port); + + addr_len = sizeof(struct sockaddr_in); + + break; + default: + addr_len = 0; + + return -1; + } + + if (0 != bind(srv_socket->fd, (struct sockaddr *) &(srv_socket->addr), addr_len)) { + log_error_write(srv, __FILE__, __LINE__, "sds", "can't bind to port", port, strerror(errno)); + return -1; + } + + if (-1 == listen(srv_socket->fd, 128 * 8)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed: ", strerror(errno)); + return -1; + } + +#ifdef SO_ACCEPTFILTER + /* + * FreeBSD accf_http filter + * + */ + memset(&afa, 0, sizeof(afa)); + strcpy(afa.af_name, "httpready"); + if (setsockopt(srv_socket->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) { + if (errno != ENOENT) { + log_error_write(srv, __FILE__, __LINE__, "ss", "can't set accept-filter 'httpready': ", strerror(errno)); + } + } +#endif + + + if (s->is_ssl) { +#ifdef USE_OPENSSL + if (srv->ssl_is_init == 0) { + SSL_load_error_strings(); + SSL_library_init(); + srv->ssl_is_init = 1; + + if (0 == RAND_status()) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + "not enough entropy in the pool"); + return -1; + } + } + + if (NULL == (s->ssl_ctx = SSL_CTX_new(SSLv23_server_method()))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + + if (buffer_is_empty(s->ssl_pemfile)) { + log_error_write(srv, __FILE__, __LINE__, "s", "ssl.pemfile has to be set"); + return -1; + } + + if (0 > SSL_CTX_use_certificate_file(s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + + if (0 > SSL_CTX_use_PrivateKey_file (s->ssl_ctx, s->ssl_pemfile->ptr, SSL_FILETYPE_PEM)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + + if (!SSL_CTX_check_private_key(s->ssl_ctx)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + "Private key does not match the certificate public key"); + return -1; + } + srv_socket->ssl_ctx = s->ssl_ctx; +#else + + buffer_free(srv_socket->srv_token); + free(srv_socket); + + buffer_free(b); + + log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", + "ssl requested but openssl support is not compiled in"); + + return -1; +#endif + } + + srv_socket->is_ssl = s->is_ssl; + srv_socket->fde_ndx = -1; + + if (srv->srv_sockets.size == 0) { + srv->srv_sockets.size = 4; + srv->srv_sockets.used = 0; + srv->srv_sockets.ptr = malloc(srv->srv_sockets.size * sizeof(server_socket)); + } else if (srv->srv_sockets.used == srv->srv_sockets.size) { + srv->srv_sockets.size += 4; + srv->srv_sockets.ptr = realloc(srv->srv_sockets.ptr, srv->srv_sockets.size * sizeof(server_socket)); + } + + srv->srv_sockets.ptr[srv->srv_sockets.used++] = srv_socket; + + buffer_free(b); + + return 0; +} + +int network_close(server *srv) { + size_t i; + for (i = 0; i < srv->srv_sockets.used; i++) { + server_socket *srv_socket = srv->srv_sockets.ptr[i]; + + if (srv_socket->fd) { + /* check if server fd are already registered */ + if (srv_socket->fde_ndx != -1) { + fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd); + fdevent_unregister(srv->ev, srv_socket->fd); + } + + close(srv_socket->fd); + } + + buffer_free(srv_socket->srv_token); + + free(srv_socket); + } + + free(srv->srv_sockets.ptr); + + return 0; +} + +int network_init(server *srv) { + buffer *b; + size_t i; + + b = buffer_init(); + + buffer_copy_string_buffer(b, srv->srvconf.bindhost); + buffer_append_string(b, ":"); + buffer_append_long(b, srv->srvconf.port); + + if (0 != network_server_init(srv, b, srv->config_storage[0])) { + return -1; + } + buffer_free(b); + + /* check for $SERVER["socket"] */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + specific_config *s = srv->config_storage[i]; + + /* not our stage */ + if (!buffer_is_equal_string(dc->comp_key, CONST_STR_LEN("SERVERsocket"))) continue; + + if (dc->cond != CONFIG_COND_EQ) { + log_error_write(srv, __FILE__, __LINE__, "s", "only == is allowed for $SERVER[\"socket\"]."); + + return -1; + } + + if (0 != network_server_init(srv, dc->match.string, s)) { + return -1; + } + } + + + return 0; +} + +int network_register_fdevents(server *srv) { + size_t i; + + fdevent_reset(srv->ev); + + /* register fdevents after reset */ + for (i = 0; i < srv->srv_sockets.used; i++) { + server_socket *srv_socket = srv->srv_sockets.ptr[i]; + + fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket); + fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN); + } + return 0; +} + +int network_write_chunkqueue(server *srv, connection *con, chunkqueue *cq) { + int ret = -1, i; + off_t written = 0; + + server_socket *srv_socket = con->srv_socket; + + if (con->conf.global_kbytes_per_second && + *(con->conf.global_bytes_per_second_cnt_ptr) > con->conf.global_kbytes_per_second * 1024) { + /* we reached the global traffic limit */ + + con->traffic_limit_reached = 1; + joblist_append(srv, con); + + return 1; + } + + written = con->bytes_written; + +#ifdef TCP_CORK + /* Linux: put a cork into the socket as we want to combine the write() calls */ + i = 1; + setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &i, sizeof(i)); +#endif + + if (srv_socket->is_ssl) { +#ifdef USE_OPENSSL + ret = network_write_chunkqueue_openssl(srv, con, cq); +#endif + } else { + /* dispatch call */ +#if defined USE_LINUX_SENDFILE + ret = network_write_chunkqueue_linuxsendfile(srv, con, cq); +#elif defined USE_FREEBSD_SENDFILE + ret = network_write_chunkqueue_freebsdsendfile(srv, con, cq); +#elif defined USE_SOLARIS_SENDFILEV + ret = network_write_chunkqueue_solarissendfilev(srv, con, cq); +#elif defined USE_WRITEV + ret = network_write_chunkqueue_writev(srv, con, cq); +#else + ret = network_write_chunkqueue_write(srv, con, cq); +#endif + } + + if (ret >= 0) { + /* + * map the return code + * + * -1 -> -1 + * >0 -> (everything written) 0 + * (not finished yet) 1 + * + * ret means: + * - <ret> chunks are unused now + * + */ + + chunk *c, *pc = NULL; + + for (i = 0, c = cq->first; i < ret; i++, c = c->next) { + buffer_reset(c->data.mem); + + if (i == ret - 1) pc = c; + } + + if (c) { + /* there is still something to write */ + + if (c != cq->first) { + /* move the first few buffers to unused */ + + assert(pc); + + pc->next = cq->unused; + cq->unused = cq->first; + cq->first = c; + } + + ret = 1; + } else { + /* everything is written */ + chunkqueue_reset(cq); + + ret = 0; + } + } + +#ifdef TCP_CORK + i = 0; + setsockopt(con->fd, IPPROTO_TCP, TCP_CORK, &i, sizeof(i)); +#endif + + written = con->bytes_written - written; + con->bytes_written_cur_second += written; + + *(con->conf.global_bytes_per_second_cnt_ptr) += written; + + if (con->conf.kbytes_per_second && + (con->bytes_written_cur_second > con->conf.kbytes_per_second * 1024)) { + /* we reached the traffic limit */ + + con->traffic_limit_reached = 1; + joblist_append(srv, con); + } + return ret; +} diff --git a/src/network.h b/src/network.h new file mode 100644 index 00000000..99c75966 --- /dev/null +++ b/src/network.h @@ -0,0 +1,13 @@ +#ifndef _NETWORK_H_ +#define _NETWORK_H_ + +#include "server.h" + +int network_write_chunkqueue(server *srv, connection *con, chunkqueue *c); + +int network_init(server *srv); +int network_close(server *srv); + +int network_register_fdevents(server *srv); + +#endif diff --git a/src/network_backends.h b/src/network_backends.h new file mode 100644 index 00000000..8238c72b --- /dev/null +++ b/src/network_backends.h @@ -0,0 +1,54 @@ +#ifndef _NETWORK_BACKENDS_H_ +#define _NETWORK_BACKENDS_H_ + +#include "config.h" + +#include <sys/types.h> + +/* on linux 2.4.x you get either sendfile or LFS */ +#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) && defined HAVE_WRITEV && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN +# define USE_LINUX_SENDFILE +# include <sys/sendfile.h> +# include <sys/uio.h> +#endif + +#if defined HAVE_SYS_UIO_H && defined HAVE_SENDFILE && defined HAVE_WRITEV && defined(__FreeBSD__) +# define USE_FREEBSD_SENDFILE +# include <sys/uio.h> +#endif + +#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILEV && defined HAVE_WRITEV && defined(__sun) +# define USE_SOLARIS_SENDFILEV +# include <sys/sendfile.h> +# include <sys/uio.h> +#endif + +#if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV +# define USE_WRITEV +# include <sys/uio.h> +#endif + +#if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP +# define USE_MMAP +# include <sys/mman.h> +/* NetBSD 1.3.x needs it */ +# ifndef MAP_FAILED +# define MAP_FAILED -1 +# endif +#endif + +#if defined HAVE_SYS_UIO_H && defined HAVE_WRITEV && defined HAVE_SEND_FILE && defined(__aix) +# define USE_AIX_SENDFILE +#endif + +#include "base.h" + + +int network_write_chunkqueue_write(server *srv, connection *con, chunkqueue *cq); +int network_write_chunkqueue_writev(server *srv, connection *con, chunkqueue *cq); +int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, chunkqueue *cq); +int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, chunkqueue *cq); +int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, chunkqueue *cq); +int network_write_chunkqueue_openssl(server *srv, connection *con, chunkqueue *cq); + +#endif diff --git a/src/network_freebsd_sendfile.c b/src/network_freebsd_sendfile.c new file mode 100644 index 00000000..8953fb68 --- /dev/null +++ b/src/network_freebsd_sendfile.c @@ -0,0 +1,192 @@ +#include "network_backends.h" + +#ifdef USE_FREEBSD_SENDFILE + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" + + +#ifndef UIO_MAXIOV +# ifdef __FreeBSD__ +/* FreeBSD 4.7, 4.9 defined it in sys/uio.h only if _KERNEL is specified */ +# define UIO_MAXIOV 1024 +# endif +#endif + +int network_write_chunkqueue_freebsdsendfile(server *srv, connection *con, chunkqueue *cq) { + const int fd = con->fd; + chunk *c; + size_t chunks_written = 0; + + for(c = cq->first; c; c = c->next, chunks_written++) { + int chunk_finished = 0; + + switch(c->type) { + case MEM_CHUNK: { + char * offset; + size_t toSend; + ssize_t r; + + size_t num_chunks, i; + struct iovec chunks[UIO_MAXIOV]; + chunk *tc; + size_t num_bytes = 0; + + /* we can't send more then SSIZE_MAX bytes in one chunk */ + + /* build writev list + * + * 1. limit: num_chunks < UIO_MAXIOV + * 2. limit: num_bytes < SSIZE_MAX + */ + for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next); + + for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) { + if (tc->data.mem->used == 0) { + chunks[i].iov_base = tc->data.mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->data.mem->ptr + tc->offset; + toSend = tc->data.mem->used - 1 - tc->offset; + + chunks[i].iov_base = offset; + + /* protect the return value of writev() */ + if (toSend > SSIZE_MAX || + num_bytes + toSend > SSIZE_MAX) { + chunks[i].iov_len = SSIZE_MAX - num_bytes; + + num_chunks = i + 1; + break; + } else { + chunks[i].iov_len = toSend; + } + + num_bytes += toSend; + } + } + + if ((r = writev(fd, chunks, num_chunks)) < 0) { + switch (errno) { + case EAGAIN: + case EINTR: + r = 0; + break; + case EPIPE: + case ECONNRESET: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "writev failed:", strerror(errno), fd); + + return -1; + } + + r = 0; + } + + /* check which chunks have been written */ + + for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) { + if (r >= (ssize_t)chunks[i].iov_len) { + /* written */ + r -= chunks[i].iov_len; + tc->offset += chunks[i].iov_len; + con->bytes_written += chunks[i].iov_len; + + if (chunk_finished) { + /* skip the chunks from further touches */ + chunks_written++; + c = c->next; + } else { + /* chunks_written + c = c->next is done in the for()*/ + chunk_finished++; + } + } else { + /* partially written */ + + tc->offset += r; + con->bytes_written += r; + chunk_finished = 0; + + break; + } + } + + break; + } + case FILE_CHUNK: { + off_t offset, r; + size_t toSend; + + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->data.file.name); + return -1; + } + + offset = c->data.file.offset + c->offset; + toSend = c->data.file.length - c->offset; + + if (offset > con->fce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name); + + return -1; + } + + r = 0; + + /* FreeBSD sendfile() */ + if (-1 == sendfile(con->fce->fd, fd, offset, toSend, NULL, &r, 0)) { + if (errno != EAGAIN) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno); + + return -1; + } + } + + c->offset += r; + con->bytes_written += r; + + if (c->offset == c->data.file.length) { + chunk_finished = 1; + } + + break; + } + default: + + log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); + + return -1; + } + + if (!chunk_finished) { + /* not finished yet */ + + break; + } + } + + return chunks_written; +} + +#endif diff --git a/src/network_linux_sendfile.c b/src/network_linux_sendfile.c new file mode 100644 index 00000000..829c73b9 --- /dev/null +++ b/src/network_linux_sendfile.c @@ -0,0 +1,205 @@ +#include "network_backends.h" + +#ifdef USE_LINUX_SENDFILE +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" + + +int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, chunkqueue *cq) { + const int fd = con->fd; + chunk *c; + size_t chunks_written = 0; + + for(c = cq->first; c; c = c->next, chunks_written++) { + int chunk_finished = 0; + + switch(c->type) { + case MEM_CHUNK: { + char * offset; + size_t toSend; + ssize_t r; + + size_t num_chunks, i; + struct iovec chunks[UIO_MAXIOV]; + chunk *tc; + size_t num_bytes = 0; + + /* we can't send more then SSIZE_MAX bytes in one chunk */ + + /* build writev list + * + * 1. limit: num_chunks < UIO_MAXIOV + * 2. limit: num_bytes < SSIZE_MAX + */ + for (num_chunks = 0, tc = c; + tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; + tc = tc->next, num_chunks++); + + for (tc = c, i = 0; i < num_chunks; tc = tc->next, i++) { + if (tc->data.mem->used == 0) { + chunks[i].iov_base = tc->data.mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->data.mem->ptr + tc->offset; + toSend = tc->data.mem->used - 1 - tc->offset; + + chunks[i].iov_base = offset; + + /* protect the return value of writev() */ + if (toSend > SSIZE_MAX || + num_bytes + toSend > SSIZE_MAX) { + chunks[i].iov_len = SSIZE_MAX - num_bytes; + + num_chunks = i + 1; + break; + } else { + chunks[i].iov_len = toSend; + } + + num_bytes += toSend; + } + } + + if ((r = writev(fd, chunks, num_chunks)) < 0) { + switch (errno) { + case EAGAIN: + case EINTR: + r = 0; + break; + case EPIPE: + case ECONNRESET: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "writev failed:", strerror(errno), fd); + + return -1; + } + } + + /* check which chunks have been written */ + + for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) { + if (r >= (ssize_t)chunks[i].iov_len) { + /* written */ + r -= chunks[i].iov_len; + tc->offset += chunks[i].iov_len; + con->bytes_written += chunks[i].iov_len; + + if (chunk_finished) { + /* skip the chunks from further touches */ + chunks_written++; + c = c->next; + } else { + /* chunks_written + c = c->next is done in the for()*/ + chunk_finished++; + } + } else { + /* partially written */ + + tc->offset += r; + con->bytes_written += r; + chunk_finished = 0; + + break; + } + } + + break; + } + case FILE_CHUNK: { + ssize_t r; + off_t offset; + size_t toSend; + + switch(file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) { + case HANDLER_GO_ON: + offset = c->data.file.offset + c->offset; + /* limit the toSend to 2^31-1 bytes in a chunk */ + toSend = c->data.file.length - c->offset > ((1 << 30) - 1) ? + ((1 << 30) - 1) : c->data.file.length - c->offset; + + if (offset > con->fce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name); + + return -1; + } + + /* Linux sendfile() */ + if (-1 == (r = sendfile(fd, con->fce->fd, &offset, toSend))) { + if (errno != EAGAIN && + errno != EINTR) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile:", strerror(errno), errno); + + return -1; + } + + r = 0; + } + + break; + case HANDLER_WAIT_FOR_FD: + /* comeback later */ + + log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile (handled):", strerror(errno), errno); + + r = 0; + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->data.file.name); + + return -1; + } + + c->offset += r; + con->bytes_written += r; + + if (c->offset == c->data.file.length) { + chunk_finished = 1; + } + + break; + } + default: + + log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); + + return -1; + } + + if (!chunk_finished) { + /* not finished yet */ + + break; + } + } + + return chunks_written; +} + +#endif +#if 0 +network_linuxsendfile_init(void) { + p->write = network_linuxsendfile_write_chunkset; +} +#endif diff --git a/src/network_openssl.c b/src/network_openssl.c new file mode 100644 index 00000000..7e8e2f6a --- /dev/null +++ b/src/network_openssl.c @@ -0,0 +1,176 @@ +#include "network_backends.h" + +#ifdef USE_OPENSSL +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" + +# include <openssl/ssl.h> +# include <openssl/err.h> + +int network_write_chunkqueue_openssl(server *srv, connection *con, chunkqueue *cq) { + int ssl_r; + chunk *c; + size_t chunks_written = 0; + + for(c = cq->first; c; c = c->next) { + int chunk_finished = 0; + + switch(c->type) { + case MEM_CHUNK: { + char * offset; + size_t toSend; + ssize_t r; + + if (c->data.mem->used == 0) { + chunk_finished = 1; + break; + } + + offset = c->data.mem->ptr + c->offset; + toSend = c->data.mem->used - 1 - c->offset; + + /** + * SSL_write man-page + * + * WARNING + * When an SSL_write() operation has to be repeated because of + * SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be + * repeated with the same arguments. + * + */ + + if ((r = SSL_write(con->ssl, offset, toSend)) <= 0) { + switch ((ssl_r = SSL_get_error(con->ssl, r))) { + case SSL_ERROR_WANT_WRITE: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", + ssl_r, r, + ERR_error_string(ERR_get_error(), NULL)); + + return -1; + } + } else { + c->offset += r; + con->bytes_written += r; + } + + if (c->offset == (off_t)c->data.mem->used - 1) { + chunk_finished = 1; + } + + break; + } + case FILE_CHUNK: { + char *s; + ssize_t r; + off_t offset; + size_t toSend; +# if defined USE_MMAP + char *p; +# endif + + WP(); + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->data.file.name); + return -1; + } + + offset = c->data.file.offset + c->offset; + toSend = c->data.file.length - c->offset; + + +#if defined USE_MMAP + if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno)); + + return -1; + } + + s = p + offset; +#else + buffer_prepare_copy(srv->tmp_buf, toSend); + + lseek(con->fce->fd, offset, SEEK_SET); + if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "read failed: ", strerror(errno)); + + return -1; + } + + s = srv->tmp_buf->ptr; +#endif + + if ((r = SSL_write(con->ssl, s, toSend)) <= 0) { + switch ((ssl_r = SSL_get_error(con->ssl, r))) { + case SSL_ERROR_WANT_WRITE: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", + ssl_r, r, + ERR_error_string(ERR_get_error(), NULL)); + +#if defined USE_MMAP + munmap(p, c->data.file.length); +#endif + return -1; + } + } else { + c->offset += r; + con->bytes_written += r; + } + +#if defined USE_MMAP + munmap(p, c->data.file.length); +#endif + + if (c->offset == c->data.file.length) { + chunk_finished = 1; + } + + break; + } + default: + log_error_write(srv, __FILE__, __LINE__, "s", "type not known"); + + return -1; + } + + if (!chunk_finished) { + /* not finished yet */ + + break; + } + + chunks_written++; + + } + + return chunks_written; +} +#endif + +#if 0 +network_openssl_init(void) { + p->write_ssl = network_openssl_write_chunkset; +} +#endif diff --git a/src/network_solaris_sendfilev.c b/src/network_solaris_sendfilev.c new file mode 100644 index 00000000..2e0694c1 --- /dev/null +++ b/src/network_solaris_sendfilev.c @@ -0,0 +1,208 @@ +#include "network_backends.h" + +#ifdef USE_SOLARIS_SENDFILEV + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" + +#ifndef UIO_MAXIOV +#define UIO_MAXIOV IOV_MAX +#endif + +/** + * a very simple sendfilev() interface for solaris which can be optimised a lot more + * as solaris sendfilev() supports 'sending everythin in one syscall()' + * + * If you want such an interface and need the performance, just give me an account on + * a solaris box. + * - jan@kneschke.de + */ + + +int network_write_chunkqueue_solarissendfilev(server *srv, connection *con, chunkqueue *cq) { + const int fd = con->fd; + chunk *c; + size_t chunks_written = 0; + + for(c = cq->first; c; c = c->next, chunks_written++) { + int chunk_finished = 0; + + switch(c->type) { + case MEM_CHUNK: { + char * offset; + size_t toSend; + ssize_t r; + + size_t num_chunks, i; + struct iovec chunks[UIO_MAXIOV]; + chunk *tc; + + size_t num_bytes = 0; + + /* we can't send more then SSIZE_MAX bytes in one chunk */ + + /* build writev list + * + * 1. limit: num_chunks < UIO_MAXIOV + * 2. limit: num_bytes < SSIZE_MAX + */ + for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next); + + for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) { + if (tc->data.mem->used == 0) { + chunks[i].iov_base = tc->data.mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->data.mem->ptr + tc->offset; + toSend = tc->data.mem->used - 1 - tc->offset; + + chunks[i].iov_base = offset; + + /* protect the return value of writev() */ + if (toSend > SSIZE_MAX || + num_bytes + toSend > SSIZE_MAX) { + chunks[i].iov_len = SSIZE_MAX - num_bytes; + + num_chunks = i + 1; + break; + } else { + chunks[i].iov_len = toSend; + } + + num_bytes += toSend; + } + } + + if ((r = writev(fd, chunks, num_chunks)) < 0) { + switch (errno) { + case EAGAIN: + case EINTR: + r = 0; + break; + case EPIPE: + case ECONNRESET: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "writev failed:", strerror(errno), fd); + + return -1; + } + } + + /* check which chunks have been written */ + + for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) { + if (r >= (ssize_t)chunks[i].iov_len) { + /* written */ + r -= chunks[i].iov_len; + tc->offset += chunks[i].iov_len; + con->bytes_written += chunks[i].iov_len; + + if (chunk_finished) { + /* skip the chunks from further touches */ + chunks_written++; + c = c->next; + } else { + /* chunks_written + c = c->next is done in the for()*/ + chunk_finished++; + } + } else { + /* partially written */ + + tc->offset += r; + con->bytes_written += r; + chunk_finished = 0; + + log_error_write(srv, __FILE__, __LINE__, "sdd", + "partially write: ", r, fd); + + break; + } + } + + break; + } + case FILE_CHUNK: { + ssize_t r; + off_t offset; + size_t toSend, written; + sendfilevec_t fvec; + + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->data.file.name); + return -1; + } + + offset = c->data.file.offset + c->offset; + toSend = c->data.file.length - c->offset; + + if (offset > con->fce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name); + + return -1; + } + + fvec.sfv_fd = con->fce->fd; + fvec.sfv_flag = 0; + fvec.sfv_off = offset; + fvec.sfv_len = toSend; + + /* Solaris sendfilev() */ + if (-1 == (r = sendfilev(fd, &fvec, 1, &written))) { + if (errno != EAGAIN) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno); + + return -1; + } + + r = 0; + } + + c->offset += written; + con->bytes_written += written; + + if (c->offset == c->data.file.length) { + chunk_finished = 1; + } + + break; + } + default: + + log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); + + return -1; + } + + if (!chunk_finished) { + /* not finished yet */ + + break; + } + } + + return chunks_written; +} + +#endif diff --git a/src/network_write.c b/src/network_write.c new file mode 100644 index 00000000..542120b9 --- /dev/null +++ b/src/network_write.c @@ -0,0 +1,188 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" + +#include "sys-socket.h" + +#include "network_backends.h" + +int network_write_chunkqueue_write(server *srv, connection *con, chunkqueue *cq) { + const int fd = con->fd; + chunk *c; + size_t chunks_written = 0; + + for(c = cq->first; c; c = c->next) { + int chunk_finished = 0; + + switch(c->type) { + case MEM_CHUNK: { + char * offset; + size_t toSend; + ssize_t r; + + if (c->data.mem->used == 0) { + chunk_finished = 1; + break; + } + + offset = c->data.mem->ptr + c->offset; + toSend = c->data.mem->used - 1 - c->offset; +#ifdef __WIN32 + if ((r = send(fd, offset, toSend, 0)) < 0) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed: ", strerror(errno), fd); + + return -1; + } +#else + if ((r = write(fd, offset, toSend)) < 0) { + log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed: ", strerror(errno), fd); + + return -1; + } +#endif + + c->offset += r; + con->bytes_written += r; + + if (c->offset == (off_t)c->data.mem->used - 1) { + chunk_finished = 1; + } + + break; + } + case FILE_CHUNK: { +#ifdef USE_MMAP + char *p = NULL; +#endif + ssize_t r; + off_t offset; + size_t toSend; + + if (HANDLER_GO_ON != file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->data.file.name); + return -1; + } + + offset = c->data.file.offset + c->offset; + toSend = c->data.file.length - c->offset; + + if (offset > con->fce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name); + + return -1; + } + + if (-1 == con->fce->fd) { + log_error_write(srv, __FILE__, __LINE__, "sb", "fd is invalid", c->data.file.name); + + return -1; + } + +#if defined USE_MMAP + /* check if the mapping fits */ + if (con->fce->mmap_p && + con->fce->mmap_length != con->fce->st.st_size && + con->fce->mmap_offset != 0) { + munmap(con->fce->mmap_p, con->fce->mmap_length); + + con->fce->mmap_p = NULL; + } + + /* build mapping if neccesary */ + if (con->fce->mmap_p == NULL) { + if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "mmap failed: ", strerror(errno)); + + return -1; + } + con->fce->mmap_p = p; + con->fce->mmap_offset = 0; + con->fce->mmap_length = con->fce->st.st_size; + } else { + p = con->fce->mmap_p; + } + + if ((r = write(fd, p + offset, toSend)) <= 0) { + log_error_write(srv, __FILE__, __LINE__, "ss", "write failed: ", strerror(errno)); + + return -1; + } + + /* don't cache mmap()ings for files large then 64k */ + if (con->fce->mmap_length > 64 * 1024) { + munmap(con->fce->mmap_p, con->fce->mmap_length); + + con->fce->mmap_p = NULL; + } + +#else + buffer_prepare_copy(srv->tmp_buf, toSend); + + lseek(con->fce->fd, offset, SEEK_SET); + if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno)); + + return -1; + } +#ifdef __WIN32 + if (-1 == (r = send(fd, srv->tmp_buf->ptr, toSend, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno)); + + return -1; + } +#else + if (-1 == (r = write(fd, srv->tmp_buf->ptr, toSend))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno)); + + return -1; + } +#endif +#endif + c->offset += r; + con->bytes_written += r; + + if (c->offset == c->data.file.length) { + chunk_finished = 1; + } + + break; + } + default: + + log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); + + return -1; + } + + if (!chunk_finished) { + /* not finished yet */ + + break; + } + + chunks_written++; + } + + return chunks_written; +} + +#if 0 +network_write_init(void) { + p->write = network_write_write_chunkset; +} +#endif diff --git a/src/network_writev.c b/src/network_writev.c new file mode 100644 index 00000000..3421241c --- /dev/null +++ b/src/network_writev.c @@ -0,0 +1,288 @@ +#include "network_backends.h" + +#ifdef USE_WRITEV + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <stdio.h> + +#include "network.h" +#include "fdevent.h" +#include "log.h" +#include "file_cache.h" + +#ifndef UIO_MAXIOV +# if defined(__FreeBSD__) || defined(__APPLE__) || defined(__NetBSD__) +/* FreeBSD 4.7 defines it in sys/uio.h only if _KERNEL is specified */ +# define UIO_MAXIOV 1024 +# elif defined(__sgi) +/* IRIX 6.5 has sysconf(_SC_IOV_MAX) which might return 512 or bigger */ +# define UIO_MAXIOV 512 +# elif defined(__sun) +/* Solaris (and SunOS?) defines IOV_MAX instead */ +# ifndef IOV_MAX +# define UIO_MAXIOV 16 +# else +# define UIO_MAXIOV IOV_MAX +# endif +# elif defined(IOV_MAX) +# define UIO_MAXIOV IOV_MAX +# else +# error UIO_MAXIOV nor IOV_MAX are defined +# endif +#endif + +int network_write_chunkqueue_writev(server *srv, connection *con, chunkqueue *cq) { + const int fd = con->fd; + chunk *c; + size_t chunks_written = 0; + + for(c = cq->first; c; c = c->next) { + int chunk_finished = 0; + + switch(c->type) { + case MEM_CHUNK: { + char * offset; + size_t toSend; + ssize_t r; + + size_t num_chunks, i; + struct iovec chunks[UIO_MAXIOV]; + chunk *tc; + size_t num_bytes = 0; + + /* we can't send more then SSIZE_MAX bytes in one chunk */ + + /* build writev list + * + * 1. limit: num_chunks < UIO_MAXIOV + * 2. limit: num_bytes < SSIZE_MAX + */ + for(num_chunks = 0, tc = c; tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; num_chunks++, tc = tc->next); + + for(tc = c, i = 0; i < num_chunks; tc = tc->next, i++) { + if (tc->data.mem->used == 0) { + chunks[i].iov_base = tc->data.mem->ptr; + chunks[i].iov_len = 0; + } else { + offset = tc->data.mem->ptr + tc->offset; + toSend = tc->data.mem->used - 1 - tc->offset; + + chunks[i].iov_base = offset; + + /* protect the return value of writev() */ + if (toSend > SSIZE_MAX || + num_bytes + toSend > SSIZE_MAX) { + chunks[i].iov_len = SSIZE_MAX - num_bytes; + + num_chunks = i + 1; + break; + } else { + chunks[i].iov_len = toSend; + } + + num_bytes += toSend; + } + } + + if ((r = writev(fd, chunks, num_chunks)) < 0) { + switch (errno) { + case EAGAIN: + case EINTR: + r = 0; + break; + case EPIPE: + case ECONNRESET: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "writev failed:", strerror(errno), fd); + + return -1; + } + } + + /* check which chunks have been written */ + + for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) { + if (r >= (ssize_t)chunks[i].iov_len) { + /* written */ + r -= chunks[i].iov_len; + tc->offset += chunks[i].iov_len; + con->bytes_written += chunks[i].iov_len; + + if (chunk_finished) { + /* skip the chunks from further touches */ + chunks_written++; + c = c->next; + } else { + /* chunks_written + c = c->next is done in the for()*/ + chunk_finished++; + } + } else { + /* partially written */ + + tc->offset += r; + con->bytes_written += r; + chunk_finished = 0; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdd", + "(debug) partially write: ", r, fd); +#endif + break; + } + } + + break; + } + case FILE_CHUNK: { +#ifdef USE_MMAP + char *p = NULL; +#endif + ssize_t r; + off_t offset; + size_t toSend; + + switch (file_cache_get_entry(srv, con, c->data.file.name, &(con->fce))) { + case HANDLER_GO_ON: + if (con->fce->st.st_size == 0 || + con->fce->fd == -1) { + + log_error_write(srv, __FILE__, __LINE__, "sbdd", "foo", c->data.file.name, + con->fce->st.st_size, con->fce->fd); + } + + offset = c->data.file.offset + c->offset; + toSend = c->data.file.length - c->offset; + + if (offset > con->fce->st.st_size) { + log_error_write(srv, __FILE__, __LINE__, "sb", "file was shrinked:", c->data.file.name); + + return -1; + } + +#if defined USE_MMAP + /* check if the mapping fits */ + if (con->fce->mmap_p && + con->fce->mmap_length != con->fce->st.st_size && + con->fce->mmap_offset != 0) { + munmap(con->fce->mmap_p, con->fce->mmap_length); + + con->fce->mmap_p = NULL; + } + + /* build mapping if neccesary */ + if (con->fce->mmap_p == NULL) { + if (MAP_FAILED == (p = mmap(0, con->fce->st.st_size, PROT_READ, MAP_SHARED, con->fce->fd, 0))) { + log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", + strerror(errno), c->data.file.name, con->fce->fd); + + return -1; + } + con->fce->mmap_p = p; + con->fce->mmap_offset = 0; + con->fce->mmap_length = con->fce->st.st_size; + } else { + p = con->fce->mmap_p; + } + + if ((r = write(fd, p + offset, toSend)) <= 0) { + switch (errno) { + case EAGAIN: + case EINTR: + r = 0; + break; + case EPIPE: + case ECONNRESET: + return -2; + default: + log_error_write(srv, __FILE__, __LINE__, "ssd", + "write failed:", strerror(errno), fd); + + return -1; + } + } + + /* don't cache mmap()ings for files large then 64k */ + if (con->fce->mmap_length > 64 * 1024) { + munmap(con->fce->mmap_p, con->fce->mmap_length); + + con->fce->mmap_p = NULL; + } + +#else + buffer_prepare_copy(srv->tmp_buf, toSend); + + lseek(con->fce->fd, offset, SEEK_SET); + if (-1 == (toSend = read(con->fce->fd, srv->tmp_buf->ptr, toSend))) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "read:", strerror(errno)); + + return -1; + } + + if (-1 == (r = write(fd, srv->tmp_buf->ptr, toSend))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "write: ", strerror(errno)); + + return -1; + } +#endif + + + break; + case HANDLER_WAIT_FOR_FD: + + log_error_write(srv, __FILE__, __LINE__, "ssd", "writev (handled):", strerror(errno), errno); + + r = 0; + + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sb", + strerror(errno), c->data.file.name); + return -1; + } + + c->offset += r; + con->bytes_written += r; + + if (c->offset == c->data.file.length) { + chunk_finished = 1; + } + + break; + } + default: + + log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); + + return -1; + } + + if (!chunk_finished) { + /* not finished yet */ + + break; + } + + chunks_written++; + } + + return chunks_written; +} + +#endif diff --git a/src/plugin.c b/src/plugin.c new file mode 100644 index 00000000..c16781b8 --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,433 @@ +#include <string.h> +#include <stdlib.h> + +#include <stdio.h> + +#include "plugin.h" +#include "log.h" +#include "config.h" + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif + +#ifndef __WIN32 +#include <dlfcn.h> +#endif +/* + * + * if you change this enum to add a new callback, be sure + * - that PLUGIN_FUNC_SIZEOF is the last entry + * - that you add PLUGIN_TO_SLOT twice: + * 1. as callback-dispatcher + * 2. in plugins_call_init() + * + */ + +typedef struct { + PLUGIN_DATA; +} plugin_data; + +typedef enum { + PLUGIN_FUNC_UNSET, + PLUGIN_FUNC_HANDLE_URI_CLEAN, + PLUGIN_FUNC_HANDLE_URI_RAW, + PLUGIN_FUNC_HANDLE_PHYSICAL_PATH, + PLUGIN_FUNC_HANDLE_REQUEST_DONE, + PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, + PLUGIN_FUNC_HANDLE_TRIGGER, + PLUGIN_FUNC_HANDLE_SIGHUP, + PLUGIN_FUNC_HANDLE_SUBREQUEST, + PLUGIN_FUNC_HANDLE_SUBREQUEST_START, + PLUGIN_FUNC_HANDLE_JOBLIST, + PLUGIN_FUNC_HANDLE_DOCROOT, + PLUGIN_FUNC_CONNECTION_RESET, + PLUGIN_FUNC_INIT, + PLUGIN_FUNC_CLEANUP, + PLUGIN_FUNC_SET_DEFAULTS, + + PLUGIN_FUNC_SIZEOF +} plugin_t; + +static plugin *plugin_init(void) { + plugin *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +static void plugin_free(plugin *p) { + int use_dlclose = 1; + if (p->name) buffer_free(p->name); +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) use_dlclose = 0; +#endif + + if (use_dlclose && p->lib) { +#ifdef __WIN32 + FreeLibrary(p->lib); +#else + dlclose(p->lib); +#endif + } + + free(p); +} + +static int plugins_register(server *srv, plugin *p) { + plugin **ps; + if (0 == srv->plugins.size) { + srv->plugins.size = 4; + srv->plugins.ptr = malloc(srv->plugins.size * sizeof(*ps)); + srv->plugins.used = 0; + } else if (srv->plugins.used == srv->plugins.size) { + srv->plugins.size += 4; + srv->plugins.ptr = realloc(srv->plugins.ptr, srv->plugins.size * sizeof(*ps)); + } + + ps = srv->plugins.ptr; + ps[srv->plugins.used++] = p; + + return 0; +} + +/** + * + * + * + */ + + +int plugins_load(server *srv) { + plugin *p; + int (*init)(plugin *pl); + const char *error; + size_t i; + + for (i = 0; i < srv->srvconf.modules->used; i++) { + data_string *d = (data_string *)srv->srvconf.modules->data[i]; + char *modules = d->value->ptr; + char *inst; + + buffer_reset(srv->tmp_buf); + /* for make distcheck */ + if (NULL != (inst = strstr(LIBRARY_DIR, "/_inst/lib"))) { + buffer_copy_string_len(srv->tmp_buf, LIBRARY_DIR, strlen(LIBRARY_DIR) - strlen(inst)); + buffer_append_string(srv->tmp_buf, "/_build/src/.libs"); + buffer_append_string(srv->tmp_buf, inst + strlen("/_inst/lib") ); + } else { + buffer_copy_string(srv->tmp_buf, LIBRARY_DIR); + } + buffer_append_string(srv->tmp_buf, "/"); + buffer_append_string(srv->tmp_buf, modules); +#if defined(__WIN32) || defined(__CYGWIN__) + buffer_append_string(srv->tmp_buf, ".dll"); +#else + buffer_append_string(srv->tmp_buf, ".so"); +#endif + + p = plugin_init(); +#ifdef __WIN32 + if (NULL == (p->lib = LoadLibrary(srv->tmp_buf->ptr))) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + log_error_write(srv, __FILE__, __LINE__, "ssb", "LoadLibrary() failed", + lpMsgBuf, srv->tmp_buf); + + plugin_free(p); + + return -1; + + } +#else + if (NULL == (p->lib = dlopen(srv->tmp_buf->ptr, RTLD_LAZY))) { + log_error_write(srv, __FILE__, __LINE__, "ss", "dlopen() failed", + dlerror()); + + plugin_free(p); + + return -1; + } + +#endif + buffer_reset(srv->tmp_buf); + buffer_copy_string(srv->tmp_buf, modules); + buffer_append_string(srv->tmp_buf, "_plugin_init"); + +#ifdef __WIN32 + init = GetProcAddress(p->lib, srv->tmp_buf->ptr); + + if (init == NULL) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + log_error_write(srv, __FILE__, __LINE__, "sbs", "getprocaddress failed:", srv->tmp_buf, lpMsgBuf); + + plugin_free(p); + return -1; + } + +#else + *(void **)(&init) = dlsym(p->lib, srv->tmp_buf->ptr); + if ((error = dlerror()) != NULL) { + log_error_write(srv, __FILE__, __LINE__, "s", error); + + plugin_free(p); + return -1; + } + +#endif + if ((*init)(p)) { + log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed" ); + + plugin_free(p); + return -1; + } +#if 0 + log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin loaded" ); +#endif + plugins_register(srv, p); + } + + return 0; +} + +#define PLUGIN_TO_SLOT(x, y) \ + handler_t plugins_call_##y(server *srv, connection *con) {\ + plugin **slot;\ + size_t j;\ + if (!srv->plugin_slots) return HANDLER_GO_ON;\ + slot = ((plugin ***)(srv->plugin_slots))[x];\ + if (!slot) return HANDLER_GO_ON;\ + for (j = 0; j < srv->plugins.used && slot[j]; j++) { \ + plugin *p = slot[j];\ + handler_t r;\ + switch(r = p->y(srv, con, p->data)) {\ + case HANDLER_GO_ON:\ + break;\ + case HANDLER_FINISHED:\ + case HANDLER_COMEBACK:\ + case HANDLER_WAIT_FOR_EVENT:\ + case HANDLER_WAIT_FOR_FD:\ + case HANDLER_ERROR:\ + return r;\ + default:\ + log_error_write(srv, __FILE__, __LINE__, "sbs", #x, p->name, "unknown state");\ + return HANDLER_ERROR;\ + }\ + }\ + return HANDLER_GO_ON;\ + } + +/** + * plugins that use + * + * - server *srv + * - connection *con + * - void *p_d (plugin_data *) + */ + +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL_PATH, handle_physical_path) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot) +PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset) + +#undef PLUGIN_TO_SLOT + +#define PLUGIN_TO_SLOT(x, y) \ + handler_t plugins_call_##y(server *srv) {\ + plugin **slot;\ + size_t j;\ + if (!srv->plugin_slots) return HANDLER_GO_ON;\ + slot = ((plugin ***)(srv->plugin_slots))[x];\ + if (!slot) return HANDLER_GO_ON;\ + for (j = 0; j < srv->plugins.used && slot[j]; j++) { \ + plugin *p = slot[j];\ + handler_t r;\ + switch(r = p->y(srv, p->data)) {\ + case HANDLER_GO_ON:\ + break;\ + case HANDLER_FINISHED:\ + case HANDLER_COMEBACK:\ + case HANDLER_WAIT_FOR_EVENT:\ + case HANDLER_WAIT_FOR_FD:\ + case HANDLER_ERROR:\ + return r;\ + default:\ + log_error_write(srv, __FILE__, __LINE__, "sbsd", #x, p->name, "unknown state:", r);\ + return HANDLER_ERROR;\ + }\ + }\ + return HANDLER_GO_ON;\ + } + +/** + * plugins that use + * + * - server *srv + * - void *p_d (plugin_data *) + */ + +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger) +PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup) +PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup) +PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults) + +#undef PLUGIN_TO_SLOT + +#if 0 +/** + * + * special handler + * + */ +handler_t plugins_call_handle_fdevent(server *srv, const fd_conn *fdc) { + size_t i; + plugin **ps; + + ps = srv->plugins.ptr; + + for (i = 0; i < srv->plugins.used; i++) { + plugin *p = ps[i]; + if (p->handle_fdevent) { + handler_t r; + switch(r = p->handle_fdevent(srv, fdc, p->data)) { + case HANDLER_GO_ON: + break; + case HANDLER_FINISHED: + case HANDLER_COMEBACK: + case HANDLER_WAIT_FOR_EVENT: + case HANDLER_ERROR: + return r; + default: + log_error_write(srv, __FILE__, __LINE__, "d", r); + break; + } + } + } + + return HANDLER_GO_ON; +} +#endif +/** + * + * - call init function of all plugins to init the plugin-internals + * - added each plugin that supports has callback to the corresponding slot + * + * - is only called once. + */ + +handler_t plugins_call_init(server *srv) { + size_t i; + plugin **ps; + + ps = srv->plugins.ptr; + + /* fill slots */ + + srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps)); + + for (i = 0; i < srv->plugins.used; i++) { + size_t j; + /* check which calls are supported */ + + plugin *p = ps[i]; + +#define PLUGIN_TO_SLOT(x, y) \ + if (p->y) { \ + plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \ + if (!slot) { \ + slot = calloc(srv->plugins.used, sizeof(*slot));\ + ((plugin ***)(srv->plugin_slots))[x] = slot; \ + } \ + for (j = 0; j < srv->plugins.used; j++) { \ + if (slot[j]) continue;\ + slot[j] = p;\ + break;\ + }\ + } + + + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL_PATH, handle_physical_path); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist); + PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot); + PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset); + PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup); + PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults); +#undef PLUGIN_TO_SLOT + + if (p->init) { + if (NULL == (p->data = p->init())) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "plugin-init failed for module", p->name); + return HANDLER_ERROR; + } + + /* used for con->mode, DIRECT == 0, plugins above that */ + ((plugin_data *)(p->data))->id = i + 1; + + if (p->version != LIGHTTPD_VERSION_ID) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "plugin-version doesn't match lighttpd-version for", p->name); + return HANDLER_ERROR; + } + } else { + p->data = NULL; + } + } + + return HANDLER_GO_ON; +} + +void plugins_free(server *srv) { + size_t i; + plugins_call_cleanup(srv); + + for (i = 0; i < srv->plugins.used; i++) { + plugin *p = ((plugin **)srv->plugins.ptr)[i]; + + plugin_free(p); + } + + for (i = 0; srv->plugin_slots && i < PLUGIN_FUNC_SIZEOF; i++) { + plugin **slot = ((plugin ***)(srv->plugin_slots))[i]; + + if (slot) free(slot); + } + + free(srv->plugin_slots); + srv->plugin_slots = NULL; + + free(srv->plugins.ptr); + srv->plugins.ptr = NULL; +} diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 00000000..6d33a921 --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,91 @@ +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +#include "base.h" +#include "buffer.h" + +#define SERVER_FUNC(x) \ + static handler_t x(server *srv, void *p_d) + +#define CONNECTION_FUNC(x) \ + static handler_t x(server *srv, connection *con, void *p_d) + +#define INIT_FUNC(x) \ + static void *x() + +#define FREE_FUNC SERVER_FUNC +#define TRIGGER_FUNC SERVER_FUNC +#define SETDEFAULTS_FUNC SERVER_FUNC +#define SIGHUP_FUNC SERVER_FUNC + +#define SUBREQUEST_FUNC CONNECTION_FUNC +#define JOBLIST_FUNC CONNECTION_FUNC +#define PHYSICALPATH_FUNC CONNECTION_FUNC +#define REQUESTDONE_FUNC CONNECTION_FUNC +#define URIHANDLER_FUNC CONNECTION_FUNC + +#define PLUGIN_DATA size_t id + +typedef struct { + size_t version; + + buffer *name; /* name of the plugin */ + + void *(* init) (); + handler_t (* set_defaults) (server *srv, void *p_d); + handler_t (* cleanup) (server *srv, void *p_d); + /* is called ... */ + handler_t (* handle_trigger) (server *srv, void *p_d); /* once a second */ + handler_t (* handle_sighup) (server *srv, void *p_d); /* at a signup */ + + handler_t (* handle_uri_raw) (server *srv, connection *con, void *p_d); /* after uri_raw is set */ + handler_t (* handle_uri_clean) (server *srv, connection *con, void *p_d); /* after uri is set */ + handler_t (* handle_docroot) (server *srv, connection *con, void *p_d); /* getting the document-root */ + handler_t (* handle_physical_path) (server *srv, connection *con, void *p_d); /* after the physical path is set */ + handler_t (* handle_request_done) (server *srv, connection *con, void *p_d); /* at the end of a request */ + handler_t (* handle_connection_close)(server *srv, connection *con, void *p_d); /* at the end of a connection */ + handler_t (* handle_joblist) (server *srv, connection *con, void *p_d); /* after all events are handled */ + + + + handler_t (* handle_subrequest_start)(server *srv, connection *con, void *p_d); + + /* when a handler for the request + * has to be found + */ + handler_t (* handle_subrequest) (server *srv, connection *con, void *p_d); /* */ + handler_t (* connection_reset) (server *srv, connection *con, void *p_d); /* */ + void *data; + + /* dlopen handle */ + void *lib; +} plugin; + +int plugins_load(server *srv); +void plugins_free(server *srv); + +handler_t plugins_call_handle_uri_raw(server *srv, connection *con); +handler_t plugins_call_handle_uri_clean(server *srv, connection *con); +handler_t plugins_call_handle_subrequest_start(server *srv, connection *con); +handler_t plugins_call_handle_subrequest(server *srv, connection *con); +handler_t plugins_call_handle_request_done(server *srv, connection *con); +handler_t plugins_call_handle_docroot(server *srv, connection *con); +handler_t plugins_call_handle_connection_close(server *srv, connection *con); +handler_t plugins_call_handle_joblist(server *srv, connection *con); +handler_t plugins_call_handle_physical_path(server *srv, connection *con); +handler_t plugins_call_connection_reset(server *srv, connection *con); + +handler_t plugins_call_handle_trigger(server *srv); +handler_t plugins_call_handle_sighup(server *srv); + +handler_t plugins_call_init(server *srv); +handler_t plugins_call_set_defaults(server *srv); +handler_t plugins_call_cleanup(server *srv); + +int config_insert_values_global(server *srv, array *ca, const config_values_t *cv); +int config_insert_values_internal(server *srv, array *ca, const config_values_t *cv); +int config_setup_connection(server *srv, connection *con); +int config_patch_connection(server *srv, connection *con, const char *stage, size_t stage_len); +int config_check_cond(server *srv, connection *con, data_config *dc); + +#endif diff --git a/src/request.c b/src/request.c new file mode 100644 index 00000000..bbffd1c9 --- /dev/null +++ b/src/request.c @@ -0,0 +1,937 @@ +#include <sys/stat.h> + +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include "request.h" +#include "keyvalue.h" +#include "log.h" + +static int request_check_hostname(server *srv, connection *con, buffer *host) { + enum { DOMAINLABEL, TOPLABEL } stage = TOPLABEL; + size_t i; + int label_len = 0; + size_t host_len; + char *colon; + int is_ip = -1; /* -1 don't know yet, 0 no, 1 yes */ + int level = 0; + + UNUSED(srv); + UNUSED(con); + + /* + * hostport = host [ ":" port ] + * host = hostname | IPv4address | IPv6address + * hostname = *( domainlabel "." ) toplabel [ "." ] + * domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + * toplabel = alpha | alpha *( alphanum | "-" ) alphanum + * IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit + * IPv6address = "[" ... "]" + * port = *digit + */ + + /* no Host: */ + if (!host || host->used == 0) return 0; + + host_len = host->used - 1; + + /* IPv6 adress */ + if (host->ptr[0] == '[') { + char *c = host->ptr + 1; + int colon_cnt = 0; + + /* check portnumber */ + for (; *c && *c != ']'; c++) { + if (*c == ':') { + if (++colon_cnt > 7) { + return -1; + } + } else if (!light_isxdigit(*c)) { + return -1; + } + } + + /* missing ] */ + if (!*c) { + return -1; + } + + /* check port */ + if (*(c+1) == ':') { + for (c += 2; *c; c++) { + if (!light_isdigit(*c)) { + return -1; + } + } + } + return 0; + } + + if (NULL != (colon = memchr(host->ptr, ':', host_len))) { + char *c = colon + 1; + + /* check portnumber */ + for (; *c; c++) { + if (!light_isdigit(*c)) return -1; + } + + /* remove the port from the host-len */ + host_len = colon - host->ptr; + } + + /* Host is empty */ + if (host_len == 0) return -1; + + /* scan from the right and skip the \0 */ + for (i = host_len - 1; i + 1 > 0; i--) { + char c = host->ptr[i]; + + switch (stage) { + case TOPLABEL: + if (c == '.') { + /* only switch stage, if this is not the last character */ + if (i != host_len - 1) { + if (label_len == 0) { + return -1; + } + + /* check the first character at right of the dot */ + if (is_ip == 0) { + if (!light_isalpha(host->ptr[i+1])) { + return -1; + } + } else if (!light_isdigit(host->ptr[i+1])) { + is_ip = 0; + } else if ('-' == host->ptr[i+1]) { + return -1; + } else { + /* just digits */ + is_ip = 1; + } + + stage = DOMAINLABEL; + + label_len = 0; + level++; + } else if (i == 0) { + /* just a dot and nothing else is evil */ + return -1; + } + } else if (i == 0) { + /* the first character of the hostname */ + if (!light_isalpha(c)) { + return -1; + } + label_len++; + } else { + if (c != '-' && !light_isalnum(c)) { + return -1; + } + if (is_ip == -1) { + if (!light_isdigit(c)) is_ip = 0; + } + label_len++; + } + + break; + case DOMAINLABEL: + if (is_ip == 1) { + if (c == '.') { + if (label_len == 0) { + return -1; + } + + label_len = 0; + level++; + } else if (!light_isdigit(c)) { + return -1; + } else { + label_len++; + } + } else { + if (c == '.') { + if (label_len == 0) { + return -1; + } + + /* c is either - or alphanum here */ + if ('-' == host->ptr[i+1]) { + return -1; + } + + label_len = 0; + level++; + } else if (i == 0) { + if (!light_isalnum(c)) { + return -1; + } + label_len++; + } else { + if (c != '-' && !light_isalnum(c)) { + return -1; + } + label_len++; + } + } + + break; + } + } + + /* a IP has to consist of 4 parts */ + if (is_ip == 1 && level != 3) { + return -1; + } + + if (label_len == 0) { + return -1; + } + + return 0; +} + +#if 0 +#define DUMP_HEADER +#endif + +int http_request_split_value(array *vals, buffer *b) { + char *s; + size_t i; + int state = 0; + /* + * parse + * + * val1, val2, val3, val4 + * + * into a array (more or less a explode() incl. striping of whitespaces + */ + + if (b->used == 0) return 0; + + s = b->ptr; + + for (i =0; i < b->used - 1; ) { + char *start = NULL, *end = NULL; + data_string *ds; + + switch (state) { + case 0: /* ws */ + + /* skip ws */ + for (; (*s == ' ' || *s == '\t') && i < b->used - 1; i++, s++); + + + state = 1; + break; + case 1: /* value */ + start = s; + + for (; *s != ',' && i < b->used - 1; i++, s++); + end = s - 1; + + for (; (*end == ' ' || *end == '\t') && end > start; end--); + + if (NULL == (ds = (data_string *)array_get_unused_element(vals, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string_len(ds->value, start, end-start+1); + array_insert_unique(vals, (data_unset *)ds); + + if (*s == ',') { + state = 0; + i++; + s++; + } else { + /* end of string */ + + state = 2; + } + break; + default: + i++; + break; + } + } + return 0; +} + +int request_uri_is_valid_char(char c) { + /* RFC 2396 - Appendix A */ + + /* alphanum */ + if (light_isalnum(c)) return 1; + + + switch(c) { + /* reserved */ + case ';': + case '/': + case '?': + case ':': + case '@': + case '&': + case '=': + case '+': + case '$': + case ',': + + /* mark */ + case '-': + case '_': + case '.': + case '!': + case '~': + case '*': + case '\'': + case '(': + case ')': + + /* escaped */ + case '%': + + /* fragment, should not be out in the wild $*/ + case '#': + + /* non RFC */ + case '[': + case ']': + + return 1; + } + + return 0; +} + +int http_request_parse(server *srv, connection *con) { + char *uri = NULL, *proto = NULL, *method = NULL, con_length_set; + int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding; + char *value = NULL, *key = NULL; + + enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET; + + int line = 0; + + int request_line_stage = 0; + size_t i, first; + + int done = 0; + + data_string *ds = NULL; + + /* + * Request: "^(GET|POST|HEAD) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$" + * Option : "^([-a-zA-Z]+): (.+)$" + * End : "^$" + */ + + if (con->conf.log_request_header) { + log_error_write(srv, __FILE__, __LINE__, "sdsdSb", + "fd:", con->fd, + "request-len:", con->request.request->used, + "\n", con->request.request); + } + + if (con->request_count > 1 && + con->request.request->ptr[0] == '\r' && + con->request.request->ptr[1] == '\n') { + /* we are in keep-alive and might get \r\n after a previous POST request.*/ + + buffer_copy_string_len(con->parse_request, con->request.request->ptr + 2, con->request.request->used - 1 - 2); + } else { + /* fill the local request buffer */ + buffer_copy_string_buffer(con->parse_request, con->request.request); + } + + keep_alive_set = 0; + con_length_set = 0; + + /* parse the first line of the request + * + * should be: + * + * <method> <uri> <protocol>\r\n + * */ + for (i = 0, first = 0; i < con->parse_request->used && line == 0; i++) { + char *cur = con->parse_request->ptr + i; + + switch(*cur) { + case '\r': + if (con->parse_request->ptr[i+1] == '\n') { + http_method_t r; + char *nuri = NULL; + size_t j; + + /* \r\n -> \0\0 */ + con->parse_request->ptr[i] = '\0'; + con->parse_request->ptr[i+1] = '\0'; + + buffer_copy_string_len(con->request.request_line, con->parse_request->ptr, i); + + if (request_line_stage != 2) { + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400"); + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + + proto = con->parse_request->ptr + first; + + *(uri - 1) = '\0'; + *(proto - 1) = '\0'; + + /* we got the first one :) */ + if (-1 == (r = get_http_method_key(method))) { + con->http_status = 501; + con->response.keep_alive = 0; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501"); + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + return 0; + } + + con->request.http_method = r; + + if (0 == strncmp(proto, "HTTP/1.", sizeof("HTTP/1.") - 1)) { + if (proto[7] == '1') { + con->request.http_version = con->conf.allow_http11 ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0; + } else if (proto[7] == '0') { + con->request.http_version = HTTP_VERSION_1_0; + } else { + con->http_status = 505; + + log_error_write(srv, __FILE__, __LINE__, "s", "unknown HTTP version -> 505"); + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + } else { + con->http_status = 400; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400"); + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + + if (0 == strncmp(uri, "http://", 7) && + NULL != (nuri = strchr(uri + 7, '/'))) { + /* ignore the host-part */ + + buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1); + } else { + /* everything looks good so far */ + buffer_copy_string_len(con->request.uri, uri, proto - uri - 1); + } + + /* check uri for invalid characters */ + for (j = 0; j < con->request.uri->used - 1; j++) { + if (!request_uri_is_valid_char(con->request.uri->ptr[j])) { + con->http_status = 400; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "sd", + "invalid character in URI -> 400", + con->request.uri->ptr[j]); + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + + return 0; + } + } + + buffer_copy_string_buffer(con->request.orig_uri, con->request.uri); + + con->http_status = 0; + + i++; + line++; + first = i+1; + } + break; + case ' ': + switch(request_line_stage) { + case 0: + /* GET|POST|... */ + method = con->parse_request->ptr + first; + first = i + 1; + break; + case 1: + /* /foobar/... */ + uri = con->parse_request->ptr + first; + first = i + 1; + break; + default: + /* ERROR, one space to much */ + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400"); + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } + return 0; + } + + request_line_stage++; + break; + } + } + + in_folding = 0; + + for (; i < con->parse_request->used && !done; i++) { + char *cur = con->parse_request->ptr + i; + + if (is_key) { + size_t j; + int got_colon = 0; + + /** + * 1*<any CHAR except CTLs or separators> + * CTLs == 0-31 + 127 + * + */ + switch(*cur) { + case ':': + is_key = 0; + + value = cur + 1; + + if (is_ws_after_key == 0) { + key_len = i - first; + } + is_ws_after_key = 0; + + break; + case '(': + case ')': + case '<': + case '>': + case '@': + case ',': + case ';': + case '\\': + case '\"': + case '/': + case '[': + case ']': + case '?': + case '=': + case '{': + case '}': + con->http_status = 400; + con->keep_alive = 0; + con->response.keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "sbsds", + "invalid character in key", con->request.request, cur, *cur, "-> 400"); + return 0; + case ' ': + case '\t': + if (i == first) { + is_key = 0; + in_folding = 1; + value = cur; + + break; + } + + + key_len = i - first; + + /* skip every thing up to the : */ + for (j = 1; !got_colon; j++) { + switch(con->parse_request->ptr[j + i]) { + case ' ': + case '\t': + /* skip WS */ + continue; + case ':': + /* ok, done */ + + i += j - 1; + got_colon = 1; + + break; + default: + /* error */ + + log_error_write(srv, __FILE__, __LINE__, "s", "WS character in key -> 400"); + + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + return 0; + } + } + + break; + case '\r': + if (con->parse_request->ptr[i+1] == '\n' && i == first) { + /* End of Header */ + con->parse_request->ptr[i] = '\0'; + con->parse_request->ptr[i+1] = '\0'; + + i++; + + done = 1; + + break; + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "CR without LF -> 400"); + + con->http_status = 400; + con->keep_alive = 0; + con->response.keep_alive = 0; + return 0; + } + /* fall thru */ + case 0: /* illegal characters (faster than a if () :) */ + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 10: + case 11: + case 12: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 127: + con->http_status = 400; + con->keep_alive = 0; + con->response.keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "sbsds", + "CTL character in key", con->request.request, cur, *cur, "-> 400"); + + return 0; + default: + /* ok */ + break; + } + } else { + switch(*cur) { + case '\r': + if (con->parse_request->ptr[i+1] == '\n') { + /* End of Headerline */ + con->parse_request->ptr[i] = '\0'; + con->parse_request->ptr[i+1] = '\0'; + + if (in_folding) { + if (!ds) { + /* 400 */ + + log_error_write(srv, __FILE__, __LINE__, "s", "WS at the start of first line -> 400"); + + con->http_status = 400; + con->keep_alive = 0; + con->response.keep_alive = 0; + return 0; + } + buffer_append_string(ds->value, value); + } else { + int s_len; + key = con->parse_request->ptr + first; + + s_len = cur - value; + + if (s_len > 0) { + int cmp = 0; + if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) { + ds = data_string_init(); + } + buffer_copy_string_len(ds->key, key, key_len); + buffer_copy_string_len(ds->value, value, s_len); + + /* retreive values + * + * + * the list of options is sorted to simplify the search + */ + + if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) { + array *vals; + size_t vi; + + /* split on , */ + + vals = srv->split_vals; + + array_reset(vals); + + http_request_split_value(vals, ds->value); + + for (vi = 0; vi < vals->used; vi++) { + data_string *dsv = (data_string *)vals->data[vi]; + + if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) { + keep_alive_set = HTTP_CONNECTION_KEEPALIVE; + + break; + } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) { + keep_alive_set = HTTP_CONNECTION_CLOSE; + + break; + } + } + + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) { + char *err; + unsigned long int r; + size_t j; + + if (ds->value->used == 0) SEGFAULT(); + + for (j = 0; j < ds->value->used - 1; j++) { + char c = ds->value->ptr[j]; + if (!isdigit((unsigned char)c)) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "content-length broken:", ds->value, "-> 400"); + + con->http_status = 400; + con->keep_alive = 0; + + array_insert_unique(con->request.headers, (data_unset *)ds); + return 0; + } + } + + r = strtoul(ds->value->ptr, &err, 10); + + if (*err == '\0') { + con_length_set = 1; + con->request.content_length = r; + } else { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "content-length broken:", ds->value, "-> 400"); + + con->http_status = 400; + con->keep_alive = 0; + + array_insert_unique(con->request.headers, (data_unset *)ds); + return 0; + } + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) { + con->request.http_content_type = ds->value->ptr; + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Expect")))) { + /* HTTP 2616 8.2.3 + * Expect: 100-continue + * + * -> (10.1.1) 100 (read content, process request, send final status-code) + * -> (10.4.18) 417 (close) + * + * (not handled at all yet, we always send 417 here) + */ + + con->http_status = 417; + con->keep_alive = 0; + + array_insert_unique(con->request.headers, (data_unset *)ds); + return 0; + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) { + con->request.http_host = ds->value; + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) { + con->request.http_if_modified_since = ds->value->ptr; + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) { + con->request.http_if_none_match = ds->value->ptr; + } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Range")))) { + /* bytes=.*-.* */ + + if (0 == strncasecmp(ds->value->ptr, "bytes=", 6) && + NULL != strchr(ds->value->ptr+6, '-')) { + + con->request.http_range = ds->value->ptr + 6; + } + } + + array_insert_unique(con->request.headers, (data_unset *)ds); + } else { + /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */ + } + } + + i++; + first = i+1; + is_key = 1; + value = 0; + key_len = 0; + in_folding = 0; + } else { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "CR without LF", con->request.request, "-> 400"); + + con->http_status = 400; + con->keep_alive = 0; + con->response.keep_alive = 0; + return 0; + } + break; + case ' ': + case '\t': + /* strip leading WS */ + if (value == cur) value = cur+1; + default: + break; + } + } + } + + con->header_len = i; + + /* do some post-processing */ + + if (con->request.http_version == HTTP_VERSION_1_1) { + if (keep_alive_set != HTTP_CONNECTION_CLOSE) { + /* no Connection-Header sent */ + + /* HTTP/1.1 -> keep-alive default TRUE */ + con->keep_alive = 1; + } else { + con->keep_alive = 0; + } + + /* RFC 2616, 14.23 */ + if (con->request.http_host == NULL || + buffer_is_empty(con->request.http_host)) { + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400"); + return 0; + } + } else { + if (keep_alive_set == HTTP_CONNECTION_KEEPALIVE) { + /* no Connection-Header sent */ + + /* HTTP/1.0 -> keep-alive default FALSE */ + con->keep_alive = 1; + } else { + con->keep_alive = 0; + } + } + + /* check hostname field */ + if (0 != request_check_hostname(srv, con, con->request.http_host)) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "Invalid Hostname:", con->request.http_host, "-> 400"); + + con->http_status = 400; + con->response.keep_alive = 0; + con->keep_alive = 0; + + return 0; + } + + /* check if we have read post data */ + if (con->request.http_method == HTTP_METHOD_POST) { + server_socket *srv_socket = con->srv_socket; + if (con->request.http_content_type == NULL) { + log_error_write(srv, __FILE__, __LINE__, "s", + "POST request, but content-type not set"); + } + + if (con_length_set == 0) { + /* content-length is missing */ + log_error_write(srv, __FILE__, __LINE__, "s", + "POST-request, but content-length missing -> 411"); + + con->http_status = 411; + return 0; + } + + /* don't handle more the SSIZE_MAX bytes in content-length */ + if (con->request.content_length > SSIZE_MAX) { + con->http_status = 413; + + log_error_write(srv, __FILE__, __LINE__, "sds", + "request-size too long:", con->request.content_length, "-> 413"); + return 0; + } + + /* divide by 1024 as srvconf.max_request_size is in kBytes */ + if (srv_socket->max_request_size != 0 && + (con->request.content_length >> 10) > srv_socket->max_request_size) { + /* the request body itself is larger then + * our our max_request_size + */ + + con->http_status = 413; + + log_error_write(srv, __FILE__, __LINE__, "sds", + "request-size too long:", con->request.content_length, "-> 413"); + return 0; + } + + + /* we have content */ + if (con->request.content_length != 0) { + return 1; + } + } + + return 0; +} + +int http_request_header_finished(server *srv, connection *con) { + UNUSED(srv); + + if (con->request.request->used < 5) return 0; + + if (0 == memcmp(con->request.request->ptr + con->request.request->used - 5, "\r\n\r\n", 4)) return 1; + if (NULL != strstr(con->request.request->ptr, "\r\n\r\n")) return 1; + + return 0; +} diff --git a/src/request.h b/src/request.h new file mode 100644 index 00000000..cf2b07d4 --- /dev/null +++ b/src/request.h @@ -0,0 +1,9 @@ +#ifndef _REQUEST_H_ +#define _REQUEST_H_ + +#include "server.h" + +int http_request_parse(server *srv, connection *con); +int http_request_header_finished(server *srv, connection *con); + +#endif diff --git a/src/response.c b/src/response.c new file mode 100644 index 00000000..1f67d70e --- /dev/null +++ b/src/response.c @@ -0,0 +1,1432 @@ +#include <sys/types.h> +#include <sys/stat.h> + +#include <dirent.h> +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <ctype.h> +#include <assert.h> + +#include <stdio.h> + +#include "response.h" +#include "keyvalue.h" +#include "log.h" +#include "file_cache.h" +#include "etag.h" + +#include "connections.h" + +#include "plugin.h" + +#include "sys-socket.h" + +#ifdef HAVE_ATTR_ATTRIBUTES_H +#include <attr/attributes.h> +#endif + +/* + * This was 'borrowed' from tcpdump. + * + * + * This is fun. + * + * In older BSD systems, socket addresses were fixed-length, and + * "sizeof (struct sockaddr)" gave the size of the structure. + * All addresses fit within a "struct sockaddr". + * + * In newer BSD systems, the socket address is variable-length, and + * there's an "sa_len" field giving the length of the structure; + * this allows socket addresses to be longer than 2 bytes of family + * and 14 bytes of data. + * + * Some commercial UNIXes use the old BSD scheme, some use the RFC 2553 + * variant of the old BSD scheme (with "struct sockaddr_storage" rather + * than "struct sockaddr"), and some use the new BSD scheme. + * + * Some versions of GNU libc use neither scheme, but has an "SA_LEN()" + * macro that determines the size based on the address family. Other + * versions don't have "SA_LEN()" (as it was in drafts of RFC 2553 + * but not in the final version). On the latter systems, we explicitly + * check the AF_ type to determine the length; we assume that on + * all those systems we have "struct sockaddr_storage". + */ + +#ifdef HAVE_IPV6 +# ifndef SA_LEN +# ifdef HAVE_SOCKADDR_SA_LEN +# define SA_LEN(addr) ((addr)->sa_len) +# else /* HAVE_SOCKADDR_SA_LEN */ +# ifdef HAVE_STRUCT_SOCKADDR_STORAGE +static size_t get_sa_len(const struct sockaddr *addr) { + switch (addr->sa_family) { + +# ifdef AF_INET + case AF_INET: + return (sizeof (struct sockaddr_in)); +# endif + +# ifdef AF_INET6 + case AF_INET6: + return (sizeof (struct sockaddr_in6)); +# endif + + default: + return (sizeof (struct sockaddr)); + + } +} +# define SA_LEN(addr) (get_sa_len(addr)) +# else /* HAVE_SOCKADDR_STORAGE */ +# define SA_LEN(addr) (sizeof (struct sockaddr)) +# endif /* HAVE_SOCKADDR_STORAGE */ +# endif /* HAVE_SOCKADDR_SA_LEN */ +# endif /* SA_LEN */ +#endif + + + +int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen) { + data_string *ds; + + UNUSED(srv); + + if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { + ds = data_response_init(); + } + buffer_copy_string_len(ds->key, key, keylen); + buffer_copy_string_len(ds->value, value, vallen); + + array_insert_unique(con->response.headers, (data_unset *)ds); + + return 0; +} + +int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen) { + data_string *ds; + + UNUSED(srv); + + /* if there already is a key by this name overwrite the value */ + if (NULL != (ds = (data_string *)array_get_element(con->response.headers, key))) { + buffer_copy_string(ds->value, value); + + return 0; + } + + return response_header_insert(srv, con, key, keylen, value, vallen); +} + + +int http_response_write_basic_header(server *srv, connection *con) { + size_t i; + buffer *b; + + b = chunkqueue_get_prepend_buffer(con->write_queue); + + if (con->request.http_version == HTTP_VERSION_1_1) { + buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 ")); + } else { + buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 ")); + } + buffer_append_long(b, con->http_status); + buffer_append_string_len(b, CONST_STR_LEN(" ")); + buffer_append_string(b, get_http_status_name(con->http_status)); + + /* add the connection header if + * HTTP/1.1 -> close + * HTTP/1.0 -> keep-alive + */ + if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) { + BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: "); + if (con->keep_alive) { + BUFFER_APPEND_STRING_CONST(b, "keep-alive"); + } else { + BUFFER_APPEND_STRING_CONST(b, "close"); + } + } + + if (con->request.http_version == HTTP_VERSION_1_1 && + (con->parsed_response & HTTP_DATE) == 0) { + /* HTTP/1.1 requires a Date: header */ + BUFFER_APPEND_STRING_CONST(b, "\r\nDate: "); + + /* cache the generated timestamp */ + if (srv->cur_ts != srv->last_generated_date_ts) { + buffer_prepare_copy(srv->ts_date_str, 255); + + strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts))); + + srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1; + + srv->last_generated_date_ts = srv->cur_ts; + } + + buffer_append_string_buffer(b, srv->ts_date_str); + } + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked"); + } + + /* add all headers */ + for (i = 0; i < con->response.headers->used; i++) { + data_string *ds; + + ds = (data_string *)con->response.headers->data[i]; + + if (ds->value->used && ds->key->used && + 0 != strncmp(ds->key->ptr, "X-LIGHTTPD-", sizeof("X-LIGHTTPD-") - 1)) { + BUFFER_APPEND_STRING_CONST(b, "\r\n"); + buffer_append_string_buffer(b, ds->key); + BUFFER_APPEND_STRING_CONST(b, ": "); + buffer_append_string_buffer(b, ds->value); + } + } + + if (buffer_is_empty(con->conf.server_tag)) { + BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "PACKAGE"/"VERSION); + } else { + BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "); + buffer_append_string_buffer(b, con->conf.server_tag); + } + + BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n"); + + if (con->conf.log_response_header) { + log_error_write(srv, __FILE__, __LINE__, "sdsdSb", + "fd:", con->fd, + "response-header-len:", b->used - 1, + "\n", b); + } + + con->bytes_header = b->used - 1; + + return 0; +} + + +int http_response_write_header(server *srv, connection *con, + off_t file_size, + time_t last_mod) { + buffer *b; + size_t i; + + b = chunkqueue_get_prepend_buffer(con->write_queue); + + if (con->request.http_version == HTTP_VERSION_1_1) { + BUFFER_COPY_STRING_CONST(b, "HTTP/1.1 "); + } else { + BUFFER_COPY_STRING_CONST(b, "HTTP/1.0 "); + } + buffer_append_long(b, con->http_status); + BUFFER_APPEND_STRING_CONST(b, " "); + buffer_append_string(b, get_http_status_name(con->http_status)); + + if (con->request.http_version != HTTP_VERSION_1_1 || con->keep_alive == 0) { + BUFFER_APPEND_STRING_CONST(b, "\r\nConnection: "); + buffer_append_string(b, con->keep_alive ? "keep-alive" : "close"); + } + + if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { + BUFFER_APPEND_STRING_CONST(b, "\r\nTransfer-Encoding: chunked"); + } + + /* HTTP/1.1 requires a Date: header */ + BUFFER_APPEND_STRING_CONST(b, "\r\nDate: "); + + /* cache the generated timestamp */ + if (srv->cur_ts != srv->last_generated_date_ts) { + buffer_prepare_copy(srv->ts_date_str, 255); + + strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts))); + + srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1; + + srv->last_generated_date_ts = srv->cur_ts; + } + + buffer_append_string_buffer(b, srv->ts_date_str); + + /* no Last-Modified specified */ + if (last_mod && NULL == array_get_element(con->response.headers, "Last-Modified")) { + struct tm *tm; + + for (i = 0; i < FILE_CACHE_MAX; i++) { + if (srv->mtime_cache[i].mtime == last_mod) break; + + if (srv->mtime_cache[i].mtime == 0) { + srv->mtime_cache[i].mtime = last_mod; + + buffer_prepare_copy(srv->mtime_cache[i].str, 1024); + + tm = gmtime(&(srv->mtime_cache[i].mtime)); + srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, + srv->mtime_cache[i].str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", tm); + + srv->mtime_cache[i].str->used++; + break; + } + } + + if (i == FILE_CACHE_MAX) { + i = 0; + + srv->mtime_cache[i].mtime = last_mod; + buffer_prepare_copy(srv->mtime_cache[i].str, 1024); + tm = gmtime(&(srv->mtime_cache[i].mtime)); + srv->mtime_cache[i].str->used = strftime(srv->mtime_cache[i].str->ptr, + srv->mtime_cache[i].str->size - 1, + "%a, %d %b %Y %H:%M:%S GMT", tm); + srv->mtime_cache[i].str->used++; + } + + BUFFER_APPEND_STRING_CONST(b, "\r\nLast-Modified: "); + buffer_append_string_buffer(b, srv->mtime_cache[i].str); + } + + if (file_size >= 0 && con->http_status != 304) { + BUFFER_APPEND_STRING_CONST(b, "\r\nContent-Length: "); + buffer_append_off_t(b, file_size); + } + + if (con->physical.path->used && con->physical.etag->used) { + BUFFER_APPEND_STRING_CONST(b, "\r\nETag: "); + buffer_append_string_buffer(b, con->physical.etag); + } + + BUFFER_APPEND_STRING_CONST(b, "\r\nAccept-Ranges: bytes"); + + /* add all headers */ + for (i = 0; i < con->response.headers->used; i++) { + data_string *ds; + + ds = (data_string *)con->response.headers->data[i]; + + if (ds->value->used && ds->key->used && + 0 != strncmp(ds->key->ptr, "X-LIGHTTPD-", sizeof("X-LIGHTTPD-") - 1)) { + BUFFER_APPEND_STRING_CONST(b, "\r\n"); + buffer_append_string_buffer(b, ds->key); + BUFFER_APPEND_STRING_CONST(b, ": "); + buffer_append_string_buffer(b, ds->value); +#if 0 + log_error_write(srv, __FILE__, __LINE__, "bb", + ds->key, ds->value); +#endif + } + } + + if (buffer_is_empty(con->conf.server_tag)) { + BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "PACKAGE"/"VERSION); + } else { + BUFFER_APPEND_STRING_CONST(b, "\r\nServer: "); + buffer_append_string_buffer(b, con->conf.server_tag); + } + + BUFFER_APPEND_STRING_CONST(b, "\r\n\r\n"); + + con->bytes_header = b->used - 1; + + if (con->conf.log_response_header) { + log_error_write(srv, __FILE__, __LINE__, "sSb", "Response-Header:", "\n", b); + } + + return 0; +} + +static int http_response_parse_range(server *srv, connection *con) { + struct stat st; + int multipart = 0; + int error; + off_t start, end; + const char *s, *minus; + char *boundary = "fkj49sn38dcn3"; + const char *content_type = NULL; + data_string *ds; + + if (-1 == stat(con->physical.path->ptr, &st)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "stat failed: ", strerror(errno)); + return -1; + } + + start = 0; + end = st.st_size - 1; + + con->response.content_length = 0; + + if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { + content_type = ds->value->ptr; + } + + for (s = con->request.http_range, error = 0; + !error && *s && NULL != (minus = strchr(s, '-')); ) { + char *err; + long la, le; + + if (s == minus) { + /* -<stop> */ + + le = strtol(s, &err, 10); + + if (le == 0) { + /* RFC 2616 - 14.35.1 */ + + con->http_status = 416; + error = 1; + } else if (*err == '\0') { + /* end */ + s = err; + + end = st.st_size - 1; + start = st.st_size + le; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = st.st_size - 1; + start = st.st_size + le; + } else { + error = 1; + } + + } else if (*(minus+1) == '\0' || *(minus+1) == ',') { + /* <start>- */ + + la = strtol(s, &err, 10); + + if (err == minus) { + /* ok */ + + if (*(err + 1) == '\0') { + s = err + 1; + + end = st.st_size - 1; + start = la; + + } else if (*(err + 1) == ',') { + multipart = 1; + s = err + 2; + + end = st.st_size - 1; + start = la; + } else { + error = 1; + } + } else { + /* error */ + error = 1; + } + } else { + /* <start>-<stop> */ + + la = strtol(s, &err, 10); + + if (err == minus) { + le = strtol(minus+1, &err, 10); + + /* RFC 2616 - 14.35.1 */ + if (la > le) { + error = 1; + } + + if (*err == '\0') { + /* ok, end*/ + s = err; + + end = le; + start = la; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = le; + start = la; + } else { + /* error */ + + error = 1; + } + } else { + /* error */ + + error = 1; + } + } + + if (!error) { + if (start < 0) start = 0; + + /* RFC 2616 - 14.35.1 */ + if (end > st.st_size - 1) end = st.st_size - 1; + + if (start > st.st_size - 1) { + error = 1; + + con->http_status = 416; + } + } + + if (!error) { + if (multipart) { + /* write boundary-header */ + buffer *b; + + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_copy_string(b, "\r\n--"); + buffer_append_string(b, boundary); + + /* write Content-Range */ + buffer_append_string(b, "\r\nContent-Range: bytes "); + buffer_append_off_t(b, start); + buffer_append_string(b, "-"); + buffer_append_off_t(b, end); + buffer_append_string(b, "/"); + buffer_append_off_t(b, st.st_size); + + buffer_append_string(b, "\r\nContent-Type: "); + buffer_append_string(b, content_type); + + /* write END-OF-HEADER */ + buffer_append_string(b, "\r\n\r\n"); + + con->response.content_length += b->used - 1; + + } + + chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1); + con->response.content_length += end - start + 1; + } + } + + /* something went wrong */ + if (error) { + return 0; + } + + if (multipart) { + /* add boundary end */ + buffer *b; + + b = chunkqueue_get_append_buffer(con->write_queue); + + buffer_copy_string_len(b, "\r\n--", 4); + buffer_append_string(b, boundary); + buffer_append_string_len(b, "--\r\n", 4); + + con->response.content_length += b->used - 1; + + /* set header-fields */ + + buffer_copy_string(srv->range_buf, "multipart/byteranges; boundary="); + buffer_append_string(srv->range_buf, boundary); + + /* overwrite content-type */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(srv->range_buf)); + } else { + /* add Content-Range-header */ + + buffer_copy_string(srv->range_buf, "bytes "); + buffer_append_off_t(srv->range_buf, start); + buffer_append_string(srv->range_buf, "-"); + buffer_append_off_t(srv->range_buf, end); + buffer_append_string(srv->range_buf, "/"); + buffer_append_off_t(srv->range_buf, st.st_size); + + response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(srv->range_buf)); + } + + /* ok, the file is set-up */ + con->http_status = 206; + + return 0; +} + +typedef struct { + char **ptr; + size_t used; + size_t size; +} string_buffer; + +static int http_list_directory(server *srv, connection *con, buffer *dir) { + DIR *d; + struct dirent *dent; + buffer *b, *date_buf; + string_buffer *sb; + size_t i; + + if (NULL == (d = opendir(dir->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "opendir failed:", dir, strerror(errno)); + return -1; + } + + b = chunkqueue_get_append_buffer(con->write_queue); + buffer_copy_string(b, + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + " <head>\n" + " <title>Directory Listing for "); + buffer_append_string_html_encoded(b, con->uri.path->ptr); + + BUFFER_APPEND_STRING_CONST(b, + "</title>\n" + " <style type=\"text/css\">\n"); + + BUFFER_APPEND_STRING_CONST(b, + "th.dirlisting { background-color: black; color: white; }\n" + "table.dirlisting { border: black solid thin; }\n" + "td.dirlisting { background-color: #f0f0f0; }\n" + "td.dirlistingnumber { background-color: #f0f0f0; text-align: right }\n" + ); + + BUFFER_APPEND_STRING_CONST(b, + " </style>\n" + " </head>\n" + " <body>\n" + " <h2>Directory Listing for "); + buffer_append_string_html_encoded(b, con->uri.path->ptr); + BUFFER_APPEND_STRING_CONST(b,"</h2>\n <table class='dirlisting'>\n"); + BUFFER_APPEND_STRING_CONST(b," <tr class='dirlisting'><th class='dirlisting'>date</th><th class='dirlisting'>size</th><th class='dirlisting'>type</th><th class='dirlisting'>name</th></tr>\n"); + + + /* allocate memory */ + sb = calloc(1, sizeof(*sb)); + assert(sb); + + sb->ptr = NULL; + sb->used = 0; + sb->size = 0; + + while(NULL != (dent = readdir(d))) { + size_t j; + if (sb->size == 0) { + sb->size = 4; + sb->ptr = malloc(sizeof(*sb->ptr) * sb->size); + assert(sb->ptr); + } else if (sb->used == sb->size) { + sb->size += 4; + sb->ptr = realloc(sb->ptr, sizeof(*sb->ptr) * sb->size); + assert(sb->ptr); + } + + for (i = 0; i < sb->used; i++) { + if (strcmp(dent->d_name, sb->ptr[i]) < 0) { + break; + } + } + + /* <left><right> */ + /* <left>[i]<right> */ + + for (j = sb->used - 1; sb->used && j >= i && (j+1) > 0; j--) { + sb->ptr[j + 1] = sb->ptr[j]; + } + sb->ptr[i] = strdup(dent->d_name); + + sb->used++; + + } + + closedir(d); + + date_buf = buffer_init(); + buffer_prepare_copy(date_buf, 22); + for (i = 0; i < sb->used; i++) { + struct stat st; + struct tm tm; + size_t s_len = strlen(sb->ptr[i]); + + + buffer_copy_string(srv->tmp_buf, dir->ptr); + buffer_append_string(srv->tmp_buf, sb->ptr[i]); + + if (0 != stat(srv->tmp_buf->ptr, &st)) { + free(sb->ptr[i]); + continue; + } + + BUFFER_APPEND_STRING_CONST(b, + " <tr><td class='dirlisting'>"); + +#ifdef HAVE_LOCALTIME_R + /* localtime_r is faster */ + localtime_r(&(st.st_mtime), &tm); + + date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", &tm); +#else + date_buf->used = strftime(date_buf->ptr, date_buf->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(st.st_mtime))); +#endif + date_buf->used++; + + buffer_append_string_buffer(b, date_buf); + BUFFER_APPEND_STRING_CONST(b, + "</td><td class='dirlistingnumber'>"); + + buffer_append_off_t(b, st.st_size); + + /* mime type */ + BUFFER_APPEND_STRING_CONST(b, + "</td><td class='dirlisting'>"); + + if (S_ISDIR(st.st_mode)) { + buffer_append_string_rfill(b, "directory", 28); + } else { + size_t k; + unsigned short have_content_type = 0; + +#ifdef HAVE_XATTR + char attrval[128]; + int attrlen = sizeof(attrval) - 1; + + if(con->conf.use_xattr && 0 == attr_get(srv->tmp_buf->ptr, "Content-Type", attrval, &attrlen, 0)) { + attrval[attrlen] = 0; + buffer_append_string_rfill(b, attrval, 28); + have_content_type = 1; + } +#endif + + if(!have_content_type) { + for (k = 0; k < con->conf.mimetypes->used; k++) { + data_string *ds = (data_string *)con->conf.mimetypes->data[k]; + size_t ct_len; + + if (ds->key->used == 0) continue; + + ct_len = ds->key->used - 1; + + if (s_len < ct_len) continue; + + if (0 == strncmp(sb->ptr[i] + s_len - ct_len, ds->key->ptr, ct_len)) { + buffer_append_string_rfill(b, ds->value->ptr, 28); + break; + } + } + + if (k == con->conf.mimetypes->used) { + buffer_append_string_rfill(b, "application/octet-stream", 28); + } + } + } + + /* URL */ + BUFFER_APPEND_STRING_CONST(b,"</td><td class='dirlisting'><a href=\""); + /* URL encode */ + buffer_append_string_url_encoded(b, sb->ptr[i]); + if (S_ISDIR(st.st_mode)) { + BUFFER_APPEND_STRING_CONST(b,"/"); + } + BUFFER_APPEND_STRING_CONST(b,"\">"); + /* HTML encode */ + buffer_append_string_html_encoded(b, sb->ptr[i]); + if (S_ISDIR(st.st_mode)) { + BUFFER_APPEND_STRING_CONST(b,"/"); + } + BUFFER_APPEND_STRING_CONST(b,"</a></td></tr>\n"); + + + free(sb->ptr[i]); + } + + buffer_free(date_buf); + free(sb->ptr); + free(sb); + + response_header_insert(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); + + BUFFER_APPEND_STRING_CONST(b, + " </table>\n" + " </body>\n" + "</html>\n" ); + + con->file_finished = 1; + + return 0; +} + +int http_response_redirect_to_directory(server *srv, connection *con) { + buffer *o; + + o = buffer_init(); + + if (con->conf.is_ssl) { + buffer_copy_string(o, "https://"); + } else { + buffer_copy_string(o, "http://"); + } + if (con->uri.authority->used) { + buffer_append_string_buffer(o, con->uri.authority); + } else { + /* get the name of the currently connected socket */ + struct hostent *he; +#ifdef HAVE_IPV6 + char hbuf[256]; +#endif + sock_addr our_addr; + socklen_t our_addr_len; + + our_addr_len = sizeof(our_addr); + + if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) { + con->http_status = 500; + + log_error_write(srv, __FILE__, __LINE__, "ss", + "can't get sockname", strerror(errno)); + + buffer_free(o); + return 0; + } + + + /* Lookup name: secondly try to get hostname for bind address */ + switch(our_addr.plain.sa_family) { +#ifdef HAVE_IPV6 + case AF_INET6: + if (0 != getnameinfo((const struct sockaddr *)(&our_addr.ipv6), + SA_LEN((const struct sockaddr *)&our_addr.ipv6), + hbuf, sizeof(hbuf), NULL, 0, 0)) { + + char dst[INET6_ADDRSTRLEN]; + + log_error_write(srv, __FILE__, __LINE__, + "SSSS", "NOTICE: getnameinfo failed: ", + strerror(errno), ", using ip-address instead"); + + buffer_append_string(o, + inet_ntop(AF_INET6, (char *)&our_addr.ipv6.sin6_addr, + dst, sizeof(dst))); + } else { + buffer_append_string(o, hbuf); + } + break; +#endif + case AF_INET: + if (NULL == (he = gethostbyaddr((char *)&our_addr.ipv4.sin_addr, sizeof(struct in_addr), AF_INET))) { + log_error_write(srv, __FILE__, __LINE__, + "SSSS", "NOTICE: gethostbyaddr failed: ", + hstrerror(h_errno), ", using ip-address instead"); + + buffer_append_string(o, inet_ntoa(our_addr.ipv4.sin_addr)); + } else { + buffer_append_string(o, he->h_name); + } + break; + default: + log_error_write(srv, __FILE__, __LINE__, + "S", "ERROR: unsupported address-type"); + + buffer_free(o); + return -1; + } + + if (!((con->conf.is_ssl == 0 && srv->srvconf.port == 80) || + (con->conf.is_ssl == 1 && srv->srvconf.port == 443))) { + buffer_append_string(o, ":"); + buffer_append_long(o, srv->srvconf.port); + } + } + buffer_append_string_buffer(o, con->uri.path); + buffer_append_string(o, "/"); + if (!buffer_is_empty(con->uri.query)) { + buffer_append_string(o, "?"); + buffer_append_string_buffer(o, con->uri.query); + } + + response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(o)); + + con->http_status = 301; + + buffer_free(o); + + return 0; +} + + +handler_t http_response_prepare(server *srv, connection *con) { + handler_t r; + + /* looks like someone has already done a decision */ + if (con->mode == DIRECT && + (con->http_status != 0 && con->http_status != 200)) { + /* remove a packets in the queue */ + if (con->file_finished == 0) { + chunkqueue_reset(con->write_queue); + } + + return HANDLER_FINISHED; + } + + /* no decision yet, build conf->filename */ + if (con->mode == DIRECT && con->physical.path->used == 0) { + char *qstr; + + config_patch_connection(srv, con, CONST_STR_LEN("SERVERsocket")); /* SERVERsocket */ + + /** + * prepare strings + * + * - uri.path_raw + * - uri.path (secure) + * - uri.query + * + */ + + /** + * Name according to RFC 2396 + * + * - scheme + * - authority + * - path + * - query + * + * (scheme)://(authority)(path)?(query) + * + * + */ + + buffer_copy_string(con->uri.scheme, con->conf.is_ssl ? "http" : "https"); + buffer_copy_string_buffer(con->uri.authority, con->request.http_host); + + config_patch_connection(srv, con, CONST_STR_LEN("HTTPhost")); /* Host: */ + config_patch_connection(srv, con, CONST_STR_LEN("HTTPreferer")); /* Referer: */ + config_patch_connection(srv, con, CONST_STR_LEN("HTTPuseragent")); /* User-Agent: */ + config_patch_connection(srv, con, CONST_STR_LEN("HTTPcookie")); /* Cookie: */ + + /** extract query string from request.uri */ + if (NULL != (qstr = strchr(con->request.uri->ptr, '?'))) { + buffer_copy_string (con->uri.query, qstr + 1); + buffer_copy_string_len(con->uri.path_raw, con->request.uri->ptr, qstr - con->request.uri->ptr); + } else { + buffer_reset (con->uri.query); + buffer_copy_string_buffer(con->uri.path_raw, con->request.uri); + } + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- splitting Request-URI"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Request-URI : ", con->request.uri); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI-scheme : ", con->uri.scheme); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI-authority: ", con->uri.authority); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path_raw); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI-query : ", con->uri.query); + } + + /* disable keep-alive if requested */ + + if (con->request_count > con->conf.max_keep_alive_requests) { + con->keep_alive = 0; + } + + + /** + * + * call plugins + * + * - based on the raw URL + * + */ + + switch(r = plugins_call_handle_uri_raw(srv, con)) { + case HANDLER_GO_ON: + break; + case HANDLER_FINISHED: + case HANDLER_COMEBACK: + case HANDLER_WAIT_FOR_EVENT: + case HANDLER_ERROR: + return r; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "handle_uri_raw: unknown return value", r); + break; + } + + /* build filename + * + * - decode url-encodings (e.g. %20 -> ' ') + * - remove path-modifiers (e.g. /../) + */ + + + + buffer_copy_string_buffer(srv->tmp_buf, con->uri.path_raw); + buffer_urldecode(srv->tmp_buf); + buffer_path_simplify(&(srv->dot_stack), con->uri.path, srv->tmp_buf); + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- sanatising URI"); + log_error_write(srv, __FILE__, __LINE__, "sb", "URI-path : ", con->uri.path); + } + + /** + * + * call plugins + * + * - based on the clean URL + * + */ + + config_patch_connection(srv, con, CONST_STR_LEN("HTTPurl")); /* HTTPurl */ + + switch(r = plugins_call_handle_uri_clean(srv, con)) { + case HANDLER_GO_ON: + break; + case HANDLER_FINISHED: + case HANDLER_COMEBACK: + case HANDLER_WAIT_FOR_EVENT: + case HANDLER_ERROR: + return r; + default: + log_error_write(srv, __FILE__, __LINE__, ""); + break; + } + + /*** + * + * border + * + * logical filename (URI) becomes a physical filename here + * + * + * + */ + + + + + /* 1. stat() + * ... ISREG() -> ok, go on + * ... ISDIR() -> index-file -> redirect + * + * 2. pathinfo() + * ... ISREG() + * + * 3. -> 404 + * + */ + + /* + * SEARCH DOCUMENT ROOT + */ + + /* set a default */ + + buffer_copy_string_buffer(con->physical.doc_root, con->conf.document_root); + buffer_copy_string_buffer(con->physical.rel_path, con->uri.path); + + buffer_reset(con->physical.path); + + /* the docroot plugin should set the doc_root and might also set the physical.path + * for us (all vhost-plugins are supposed to set the doc_root, the alias plugin + * sets the path too) + * */ + switch(r = plugins_call_handle_docroot(srv, con)) { + case HANDLER_GO_ON: + break; + case HANDLER_FINISHED: + case HANDLER_COMEBACK: + case HANDLER_WAIT_FOR_EVENT: + case HANDLER_ERROR: + return r; + default: + log_error_write(srv, __FILE__, __LINE__, ""); + break; + } + + if (buffer_is_empty(con->physical.path)) { + /** + * create physical filename + * -> physical.path = docroot + rel_path + * + */ + + buffer_copy_string_buffer(con->physical.path, con->physical.doc_root); + BUFFER_APPEND_SLASH(con->physical.path); + if (con->physical.rel_path->ptr[0] == '/') { + buffer_append_string_len(con->physical.path, con->physical.rel_path->ptr + 1, con->physical.rel_path->used - 2); + } else { + buffer_append_string_buffer(con->physical.path, con->physical.rel_path); + } + } + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- logical -> physical"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Doc-Root :", con->physical.doc_root); + log_error_write(srv, __FILE__, __LINE__, "sb", "Rel-Path :", con->physical.rel_path); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + } + + /* + * only if we are still in DIRECT mode we check for the real existence of the file + * + */ + + if (con->mode == DIRECT) { + char *slash = NULL; + char *pathinfo = NULL; + int found = 0; + size_t k; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- handling physical path"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + + switch (file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) { + case HANDLER_WAIT_FOR_FD: + return HANDLER_WAIT_FOR_FD; + case HANDLER_ERROR: + if (errno == EACCES) { + con->http_status = 403; + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + + if (errno != ENOENT && + errno != ENOTDIR) { + /* we have no idea what happend. let's tell the user so. */ + + con->http_status = 500; + buffer_reset(con->physical.path); + + log_error_write(srv, __FILE__, __LINE__, "ssbsb", + "file not found ... or so: ", strerror(errno), + con->uri.path, + "->", con->physical.path); + + return HANDLER_FINISHED; + } + + /* not found, perhaps PATHINFO */ + buffer_copy_string_buffer(srv->tmp_buf, con->physical.rel_path); + + /* + * + * FIXME: + * + * Check for PATHINFO fall to dir of + * + * /a is a dir and + * + * /a/b/c is requested + * + */ + + do { + struct stat st; + + buffer_copy_string_buffer(con->physical.path, con->physical.doc_root); + BUFFER_APPEND_SLASH(con->physical.path); + if (slash) { + buffer_append_string_len(con->physical.path, srv->tmp_buf->ptr, slash - srv->tmp_buf->ptr); + } else { + buffer_append_string_buffer(con->physical.path, srv->tmp_buf); + } + + if (0 == stat(con->physical.path->ptr, &(st)) && + S_ISREG(st.st_mode)) { + found = 1; + break; + } + + if (pathinfo != NULL) { + *pathinfo = '\0'; + } + slash = strrchr(srv->tmp_buf->ptr, '/'); + + if (pathinfo != NULL) { + /* restore '/' */ + *pathinfo = '/'; + } + + if (slash) pathinfo = slash; + } while ((found == 0) && (slash != NULL) && (slash != srv->tmp_buf->ptr)); + + if (found == 0) { + /* no it really doesn't exists */ + con->http_status = 404; + + if (con->conf.log_file_not_found) { + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "file not found:", con->uri.path, + "->", con->physical.path); + } + + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + + + /* we have a PATHINFO */ + if (pathinfo) { + buffer_copy_string(con->request.pathinfo, pathinfo); + + /* + * shorten uri.path + */ + + con->uri.path->used -= strlen(pathinfo); + con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + + + } + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- after pathinfo check"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + log_error_write(srv, __FILE__, __LINE__, "sb", "Pathinfo :", con->request.pathinfo); + } + + /* setup the right file cache entry (FCE) */ + switch (file_cache_get_entry(srv, con, con->physical.path, &(con->fce))) { + case HANDLER_ERROR: + con->http_status = 404; + + if (con->conf.log_file_not_found) { + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "file not found:", con->uri.path, + "->", con->physical.path); + } + + return HANDLER_FINISHED; + case HANDLER_WAIT_FOR_FD: + return HANDLER_WAIT_FOR_FD; + case HANDLER_GO_ON: + break; + default: + break; + } + + break; + case HANDLER_GO_ON: + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- file found"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); + } + + if (S_ISDIR(con->fce->st.st_mode)) { + if (con->physical.path->ptr[con->physical.path->used - 2] != '/') { + /* redirect to .../ */ + + http_response_redirect_to_directory(srv, con); + + return HANDLER_FINISHED; + } else { + found = 0; + /* indexfile */ + + for (k = 0; !found && (k < con->conf.indexfiles->used); k++) { + data_string *ds = (data_string *)con->conf.indexfiles->data[k]; + + buffer_copy_string_buffer(srv->tmp_buf, con->physical.path); + buffer_append_string_buffer(srv->tmp_buf, ds->value); + + switch (file_cache_get_entry(srv, con, srv->tmp_buf, &(con->fce))) { + case HANDLER_GO_ON: + /* rewrite uri.path to the real path (/ -> /index.php) */ + buffer_append_string_buffer(con->uri.path, ds->value); + + found = 1; + break; + case HANDLER_ERROR: + + if (errno == EACCES) { + con->http_status = 403; + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + + if (errno != ENOENT && + errno != ENOTDIR) { + /* we have no idea what happend. let's tell the user so. */ + + con->http_status = 500; + buffer_reset(con->physical.path); + + log_error_write(srv, __FILE__, __LINE__, "ssbsb", + "file not found ... or so: ", strerror(errno), + con->uri.path, + "->", con->physical.path); + + return HANDLER_FINISHED; + } + + break; + default: + break; + } + } + + if (!found && + (k == con->conf.indexfiles->used)) { + /* directory listing ? */ + + buffer_reset(srv->tmp_buf); + + if (con->conf.dir_listing == 0) { + /* dirlisting disabled */ + con->http_status = 403; + } else if (0 != http_list_directory(srv, con, con->physical.path)) { + /* dirlisting failed */ + con->http_status = 403; + } + + buffer_reset(con->physical.path); + + return HANDLER_FINISHED; + } + + buffer_copy_string_buffer(con->physical.path, srv->tmp_buf); + } + } + break; + default: + break; + } + + if (!S_ISREG(con->fce->st.st_mode)) { + con->http_status = 404; + + if (con->conf.log_file_not_found) { + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "not a regular file:", con->uri.path, + "->", con->fce->name); + } + + return HANDLER_FINISHED; + } + + /* call the handlers */ + switch(r = plugins_call_handle_subrequest_start(srv, con)) { + case HANDLER_FINISHED: + /* request was handled */ + break; + case HANDLER_GO_ON: + /* request was not handled */ + break; + default: + /* something strange happend */ + return r; + } + + /* ok, noone has handled the file up to now, so we do the fileserver-stuff */ + if (r == HANDLER_GO_ON) { + /* DIRECT */ + + /* set response content-type */ + + if (buffer_is_empty(con->fce->content_type)) { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream")); + } else { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(con->fce->content_type)); + } + + /* generate e-tag */ + etag_mutate(con->physical.etag, con->fce->etag); + + /* + * 14.26 If-None-Match + * [...] + * If none of the entity tags match, then the server MAY perform the + * requested method as if the If-None-Match header field did not exist, + * but MUST also ignore any If-Modified-Since header field(s) in the + * request. That is, if no entity tags match, then the server MUST NOT + * return a 304 (Not Modified) response. + */ + + /* last-modified handling */ + if (con->http_status == 0 && + con->request.http_if_none_match) { + if (etag_is_equal(con->physical.etag, con->request.http_if_none_match)) { + if (con->request.http_method == HTTP_METHOD_GET || + con->request.http_method == HTTP_METHOD_HEAD) { + + /* check if etag + last-modified */ + if (con->request.http_if_modified_since) { + char buf[64]; + struct tm *tm; + size_t used_len; + char *semicolon; + + tm = gmtime(&(con->fce->st.st_mtime)); + strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm); + + if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) { + used_len = strlen(con->request.http_if_modified_since); + } else { + used_len = semicolon - con->request.http_if_modified_since; + } + + if (0 == strncmp(con->request.http_if_modified_since, buf, used_len)) { + con->http_status = 304; + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", + con->request.http_if_modified_since, buf); + + con->http_status = 412; + } + } else { + con->http_status = 304; + } + } else { + con->http_status = 412; + } + } + } else if (con->http_status == 0 && con->request.http_if_modified_since) { + char buf[64]; + struct tm *tm; + size_t used_len; + char *semicolon; + + tm = gmtime(&(con->fce->st.st_mtime)); + strftime(buf, sizeof(buf)-1, "%a, %d %b %Y %H:%M:%S GMT", tm); + + if (NULL == (semicolon = strchr(con->request.http_if_modified_since, ';'))) { + used_len = strlen(con->request.http_if_modified_since); + } else { + used_len = semicolon - con->request.http_if_modified_since; + } + + if (0 == strncmp(con->request.http_if_modified_since, buf, used_len)) { + con->http_status = 304; + } + } + + if (con->http_status == 0 && con->request.http_range) { + http_response_parse_range(srv, con); + } else if (con->http_status == 0) { + switch(r = plugins_call_handle_physical_path(srv, con)) { + case HANDLER_GO_ON: + break; + default: + return r; + } + } + } + } + + switch(r = plugins_call_handle_subrequest(srv, con)) { + case HANDLER_GO_ON: + /* request was not handled, looks like we are done */ + + return HANDLER_FINISHED; + case HANDLER_FINISHED: + /* request is finished */ + default: + /* something strange happend */ + return r; + } + + /* can't happen */ + return HANDLER_COMEBACK; +} diff --git a/src/response.h b/src/response.h new file mode 100644 index 00000000..e7d7dc74 --- /dev/null +++ b/src/response.h @@ -0,0 +1,18 @@ +#ifndef _RESPONSE_H_ +#define _RESPONSE_H_ + +#include "server.h" + +int http_response_parse(server *srv, connection *con); +int http_response_write_basic_header(server *srv, connection *con); +int http_response_write_header(server *srv, connection *con, + off_t file_size, + time_t last_mod); + +int response_header_insert(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen); +int response_header_overwrite(server *srv, connection *con, const char *key, size_t keylen, const char *value, size_t vallen); + +handler_t http_response_prepare(server *srv, connection *con); +int http_response_redirect_to_directory(server *srv, connection *con); + +#endif diff --git a/src/server.c b/src/server.c new file mode 100644 index 00000000..7573caef --- /dev/null +++ b/src/server.c @@ -0,0 +1,1021 @@ +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <signal.h> +#include <assert.h> +#include <locale.h> + +#include <stdio.h> + +#include "server.h" +#include "buffer.h" +#include "network.h" +#include "log.h" +#include "keyvalue.h" +#include "response.h" +#include "request.h" +#include "chunk.h" +#include "http_chunk.h" +#include "fdevent.h" +#include "connections.h" +#include "file_cache.h" +#include "plugin.h" +#include "joblist.h" + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef HAVE_PWD_H +#include <grp.h> +#include <pwd.h> +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +const char *patches[] = { "SERVERsocket", "HTTPurl", "HTTPhost", "HTTPreferer", "HTTPuseragent", "HTTPcookie", NULL }; + + +#ifndef __sgi +/* IRIX doesn't like the alarm based time() optimization */ +/* #define USE_ALARM */ +#endif + +static sig_atomic_t srv_shutdown = 0; +static sig_atomic_t handle_sig_alarm = 1; +static sig_atomic_t handle_sig_hup = 0; + +#if defined(HAVE_SIGACTION) && defined(SA_SIGINFO) +static void sigaction_handler(int sig, siginfo_t *si, void *context) { + UNUSED(si); + UNUSED(context); + + switch (sig) { + case SIGTERM: srv_shutdown = 1; break; + case SIGALRM: handle_sig_alarm = 1; break; + case SIGHUP: handle_sig_hup = 1; break; + } +} +#elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION) +static void signal_handler(int sig) { + switch (sig) { + case SIGTERM: srv_shutdown = 1; break; + case SIGALRM: handle_sig_alarm = 1; break; + case SIGHUP: handle_sig_hup = 1; break; + } +} +#endif + +#ifdef HAVE_FORK +static void daemonize(void) { +#ifdef SIGTTOU + signal(SIGTTOU, SIG_IGN); +#endif +#ifdef SIGTTIN + signal(SIGTTIN, SIG_IGN); +#endif +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + if (fork() != 0) exit(0); + + if (setsid() == -1) exit(0); + + signal(SIGHUP, SIG_IGN); + + if (fork() != 0) exit(0); + + chdir("/"); + + umask(0); +} +#endif + +static server *server_init(void) { + int i; + + server *srv = calloc(1, sizeof(*srv)); + assert(srv); +#define CLEAN(x) \ + srv->x = buffer_init(); + + CLEAN(response_header); + CLEAN(parse_full_path); + CLEAN(ts_debug_str); + CLEAN(ts_date_str); + CLEAN(error_log); + CLEAN(response_range); + CLEAN(tmp_buf); + CLEAN(file_cache_etag); + CLEAN(range_buf); + CLEAN(empty_string); + + buffer_copy_string(srv->empty_string, ""); + + CLEAN(srvconf.error_logfile); + CLEAN(srvconf.groupname); + CLEAN(srvconf.username); + CLEAN(srvconf.changeroot); + CLEAN(srvconf.bindhost); + CLEAN(srvconf.errorfile_prefix); + CLEAN(srvconf.license); + CLEAN(srvconf.event_handler); + CLEAN(srvconf.pid_file); + + CLEAN(tmp_chunk_len); +#undef CLEAN + +#define CLEAN(x) \ + srv->x = array_init(); + + CLEAN(config_context); + CLEAN(config_touched); +#undef CLEAN + + for (i = 0; i < FILE_CACHE_MAX; i++) { + srv->mtime_cache[i].str = buffer_init(); + } + + srv->cur_ts = time(NULL); + srv->startup_ts = srv->cur_ts; + + srv->conns = calloc(1, sizeof(*srv->conns)); + assert(srv->conns); + + srv->joblist = calloc(1, sizeof(*srv->joblist)); + assert(srv->joblist); + + srv->fdwaitqueue = calloc(1, sizeof(*srv->fdwaitqueue)); + assert(srv->fdwaitqueue); + + srv->file_cache = file_cache_init(); + assert(srv->file_cache); + + srv->srvconf.modules = array_init(); + + /* use syslog */ + srv->log_error_fd = -1; + + srv->split_vals = array_init(); + + + srv->dot_stack.ptr = NULL; + srv->dot_stack.used = 0; + srv->dot_stack.size = 0; + + srv->config_patches = buffer_array_init(); + for (i = 0; patches[i]; i++) { + buffer *b; + + b = buffer_array_append_get_buffer(srv->config_patches); + buffer_copy_string(b, patches[i]); + } + + return srv; +} + +static void server_free(server *srv) { + size_t i; + + for (i = 0; i < FILE_CACHE_MAX; i++) { + buffer_free(srv->mtime_cache[i].str); + } + + buffer_array_free(srv->config_patches); + +#define CLEAN(x) \ + buffer_free(srv->x); + + CLEAN(response_header); + CLEAN(parse_full_path); + CLEAN(ts_debug_str); + CLEAN(ts_date_str); + CLEAN(error_log); + CLEAN(response_range); + CLEAN(tmp_buf); + CLEAN(file_cache_etag); + CLEAN(range_buf); + CLEAN(empty_string); + + CLEAN(srvconf.error_logfile); + CLEAN(srvconf.groupname); + CLEAN(srvconf.username); + CLEAN(srvconf.changeroot); + CLEAN(srvconf.bindhost); + CLEAN(srvconf.errorfile_prefix); + CLEAN(srvconf.license); + CLEAN(srvconf.event_handler); + CLEAN(srvconf.pid_file); + + CLEAN(tmp_chunk_len); +#undef CLEAN + +#if 0 + fdevent_unregister(srv->ev, srv->fd); +#endif + fdevent_free(srv->ev); + + connections_free(srv); + free(srv->conns); + + if (srv->config_storage) { + for (i = 0; i < srv->config_context->used; i++) { + specific_config *s = srv->config_storage[i]; + buffer_free(s->document_root); + buffer_free(s->server_name); + buffer_free(s->server_tag); + buffer_free(s->ssl_pemfile); + buffer_free(s->error_handler); + array_free(s->indexfiles); + array_free(s->mimetypes); + + free(s); + } + free(srv->config_storage); + srv->config_storage = NULL; + } + +#define CLEAN(x) \ + array_free(srv->x); + + CLEAN(config_context); + CLEAN(config_touched); +#undef CLEAN + + joblist_free(srv, srv->joblist); + fdwaitqueue_free(srv, srv->fdwaitqueue); + + file_cache_free(srv, srv->file_cache); + + array_free(srv->srvconf.modules); + array_free(srv->split_vals); + + for (i = 0; i < srv->dot_stack.size; i++) { + free(srv->dot_stack.ptr[i]); + } + free(srv->dot_stack.ptr); + + free(srv); +} + +static void show_version (void) { +#ifdef USE_OPENSSL +# define TEXT_SSL " (ssl)" +#else +# define TEXT_SSL +#endif + char *b = PACKAGE "-" VERSION TEXT_SSL \ +" - a light and fast webserver\n" \ +"Build-Date: " __DATE__ " " __TIME__ "\n"; +; +#undef TEXT_SSL + write(STDOUT_FILENO, b, strlen(b)); +} + +static void show_help (void) { +#ifdef USE_OPENSSL +# define TEXT_SSL " (ssl)" +#else +# define TEXT_SSL +#endif +#ifdef HAVE_IPV6 +# define TEXT_IPV6 " -6 use IPv6\n" +#else +# define TEXT_IPV6 +#endif + char *b = PACKAGE "-" VERSION TEXT_SSL " ("__DATE__ " " __TIME__ ")" \ +" - a light and fast webserver\n" \ +"usage:\n" \ +" -f <name> filename of the config-file\n" \ +" -D don't go to background (default: go to background)\n" \ +TEXT_IPV6 \ +" -v show version\n" \ +" -h show this help\n" \ +"\n" +; +#undef TEXT_SSL +#undef TEXT_IPV6 + write(STDOUT_FILENO, b, strlen(b)); +} + +int main (int argc, char **argv) { + server *srv = NULL; + int i_am_root; + int o; + int num_childs = 0; + int pid_fd = -1, fd; + size_t i; +#ifdef HAVE_SIGACTION + struct sigaction act; +#endif +#ifdef HAVE_GETRLIMIT + struct rlimit rlim; +#endif + +#ifdef USE_ALARM + struct itimerval interval; + + interval.it_interval.tv_sec = 1; + interval.it_interval.tv_usec = 0; + interval.it_value.tv_sec = 1; + interval.it_value.tv_usec = 0; +#endif + + + /* for nice %b handling in strfime() */ + setlocale(LC_TIME, "C"); + + if (NULL == (srv = server_init())) { + fprintf(stderr, "did this really happend ?\n"); + return -1; + } + + /* init structs done */ + + srv->srvconf.port = 0; +#ifdef HAVE_GETUID + i_am_root = (getuid() == 0); +#else + i_am_root = 0; +#endif + srv->srvconf.dont_daemonize = 0; + + while(-1 != (o = getopt(argc, argv, "f:hvD"))) { + switch(o) { + case 'f': + if (config_read(srv, optarg)) { + server_free(srv); + return -1; + } + break; + case 'D': srv->srvconf.dont_daemonize = 1; break; + case 'v': show_version(); return 0; + case 'h': show_help(); return 0; + default: + show_help(); + server_free(srv); + return -1; + } + } + + if (!srv->config_storage) { + log_error_write(srv, __FILE__, __LINE__, "s", + "No configuration available. Try using -f option."); + + server_free(srv); + return -1; + } + + /* close stdin and stdout, as they are not needed */ + /* move stdin to /dev/null */ + if (-1 != (fd = open("/dev/null", O_RDONLY))) { + close(STDIN_FILENO); + dup2(fd, STDIN_FILENO); + close(fd); + } + + /* move stdout to /dev/null */ + if (-1 != (fd = open("/dev/null", O_WRONLY))) { + close(STDOUT_FILENO); + dup2(fd, STDOUT_FILENO); + close(fd); + } + + if (0 != config_set_defaults(srv)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "setting default values failed"); + server_free(srv); + return -1; + } + + /* UID handling */ +#ifdef HAVE_GETUID + if (!i_am_root && (geteuid() == 0 || getegid() == 0)) { + /* we are setuid-root */ + + log_error_write(srv, __FILE__, __LINE__, "s", + "Are you nuts ? Don't apply a SUID bit to this binary"); + + server_free(srv); + return -1; + } +#endif + + /* check document-root */ + if (srv->config_storage[0]->document_root->used <= 1) { + log_error_write(srv, __FILE__, __LINE__, "s", + "document-root is not set\n"); + + server_free(srv); + + return -1; + } + + if (plugins_load(srv)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "loading plugins finally failed"); + + plugins_free(srv); + server_free(srv); + + return -1; + } + + /* open pid file BEFORE chroot */ + if (srv->srvconf.pid_file->used) { + if (-1 == (pid_fd = open(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { + struct stat st; + if (errno != EEXIST) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno)); + return -1; + } + + if (0 != stat(srv->srvconf.pid_file->ptr, &st)) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "stating existing pid-file failed:", srv->srvconf.pid_file, strerror(errno)); + } + + if (!S_ISREG(st.st_mode)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "pid-file exists and isn't regular file:", srv->srvconf.pid_file); + return -1; + } + + if (-1 == (pid_fd = open(srv->srvconf.pid_file->ptr, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "opening pid-file failed:", srv->srvconf.pid_file, strerror(errno)); + return -1; + } + } + } + + if (i_am_root) { + struct group *grp = NULL; + struct passwd *pwd = NULL; + int use_rlimit = 1; + +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) use_rlimit = 0; +#endif + +#ifdef HAVE_GETRLIMIT + if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) { + log_error_write(srv, __FILE__, __LINE__, + "ss", "couldn't get 'max filedescriptors'", + strerror(errno)); + return -1; + } + + if (use_rlimit && srv->srvconf.max_fds) { + /* set rlimits */ + + rlim.rlim_cur = srv->srvconf.max_fds; + rlim.rlim_max = srv->srvconf.max_fds; + + if (0 != setrlimit(RLIMIT_NOFILE, &rlim)) { + log_error_write(srv, __FILE__, __LINE__, + "ss", "couldn't set 'max filedescriptors'", + strerror(errno)); + return -1; + } + } + + srv->max_fds = rlim.rlim_cur; +#else + srv->max_fds = 4096; +#endif + + if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) { + log_error_write(srv, __FILE__, __LINE__, + "s", "fdevent_init failed"); + return -1; + } + +#ifdef HAVE_PWD_H + /* set user and group */ + if (srv->srvconf.username->used) { + if (NULL == (pwd = getpwnam(srv->srvconf.username->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "can't find username", srv->srvconf.username); + return -1; + } + + if (pwd->pw_uid == 0) { + log_error_write(srv, __FILE__, __LINE__, "s", + "I will not set uid to 0\n"); + return -1; + } + } + + if (srv->srvconf.groupname->used) { + if (NULL == (grp = getgrnam(srv->srvconf.groupname->ptr))) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "can't find groupname", srv->srvconf.groupname); + return -1; + } + if (grp->gr_gid == 0) { + log_error_write(srv, __FILE__, __LINE__, "s", + "I will not set gid to 0\n"); + return -1; + } + } +#endif + /* we need root-perms for port < 1024 */ + if (0 != network_init(srv)) { + plugins_free(srv); + server_free(srv); + + return -1; + } +#ifdef HAVE_CHROOT + if (srv->srvconf.changeroot->used) { + tzset(); + + if (-1 == chroot(srv->srvconf.changeroot->ptr)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "chroot failed: ", strerror(errno)); + return -1; + } + if (-1 == chdir("/")) { + log_error_write(srv, __FILE__, __LINE__, "ss", "chdir failed: ", strerror(errno)); + return -1; + } + } +#endif +#ifdef HAVE_PWD_H + /* drop root privs */ + if (srv->srvconf.groupname->used) { + setgid(grp->gr_gid); + setgroups(0, NULL); + } + if (srv->srvconf.username->used) setuid(pwd->pw_uid); +#endif + } else { +#ifdef HAVE_GETRLIMIT + if (0 != getrlimit(RLIMIT_NOFILE, &rlim)) { + log_error_write(srv, __FILE__, __LINE__, + "ss", "couldn't get 'max filedescriptors'", + strerror(errno)); + return -1; + } + + srv->max_fds = rlim.rlim_cur; +#else + srv->max_fds = 4096; +#endif + + if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) { + log_error_write(srv, __FILE__, __LINE__, + "s", "fdevent_init failed"); + return -1; + } + + if (0 != network_init(srv)) { + plugins_free(srv); + server_free(srv); + + return -1; + } + } + + if (HANDLER_GO_ON != plugins_call_init(srv)) { + log_error_write(srv, __FILE__, __LINE__, "s", "Initialization of plugins failed. Going down."); + + plugins_free(srv); + network_close(srv); + server_free(srv); + + return -1; + } + + if (HANDLER_GO_ON != plugins_call_set_defaults(srv)) { + log_error_write(srv, __FILE__, __LINE__, "s", "Configuration of plugins failed. Going down."); + + plugins_free(srv); + network_close(srv); + server_free(srv); + + return -1; + } + + /* dump unused config-keys */ + for (i = 0; srv->config && i < srv->config->used; i++) { + data_unset *du = srv->config->data[i]; + + if (NULL == array_get_element(srv->config_touched, du->key->ptr)) { + log_error_write(srv, __FILE__, __LINE__, "sbs", + "WARNING: unknown config-key:", + du->key, + "(ignored)"); + } + } + + if (srv->config_deprecated) { + log_error_write(srv, __FILE__, __LINE__, "s", + "Configuration contains deprecated keys. Going down."); + + plugins_free(srv); + network_close(srv); + server_free(srv); + + return -1; + } +#ifdef HAVE_FORK + /* network is up, let's deamonize ourself */ + if (srv->srvconf.dont_daemonize == 0) daemonize(); +#endif + + /* write pid file */ + if (pid_fd != -1) { + buffer_copy_long(srv->tmp_buf, getpid()); + buffer_append_string(srv->tmp_buf, "\n"); + write(pid_fd, srv->tmp_buf->ptr, srv->tmp_buf->used - 1); + close(pid_fd); + pid_fd = -1; + } + + if (-1 == log_error_open(srv)) { + log_error_write(srv, __FILE__, __LINE__, "s", + "opening errorlog failed, dying"); + + plugins_free(srv); + network_close(srv); + server_free(srv); + return -1; + } + + /* kqueue needs a reset AFTER daemonize() */ + if (0 != network_register_fdevents(srv)) { + plugins_free(srv); + network_close(srv); + server_free(srv); + + return -1; + } + + /* get the current number of FDs */ + srv->cur_fds = open("/dev/null", O_RDONLY); + close(srv->cur_fds); + + +#ifdef HAVE_SIGACTION + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGUSR1, &act, NULL); +# if defined(SA_SIGINFO) + act.sa_sigaction = sigaction_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; +# else + act.sa_handler = signal_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; +# endif + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGALRM, &act, NULL); + +#elif defined(HAVE_SIGNAL) + /* ignore the SIGPIPE from sendfile() */ + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + signal(SIGALRM, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGHUP, signal_handler); +#endif + +#ifdef USE_ALARM + signal(SIGALRM, signal_handler); + + /* setup periodic timer (1 second) */ + if (setitimer(ITIMER_REAL, &interval, NULL)) { + log_error_write(srv, __FILE__, __LINE__, "setting timer failed"); + return -1; + } + + getitimer(ITIMER_REAL, &interval); +#endif + +#ifdef HAVE_FORK + /* start watcher and workers */ + num_childs = srv->srvconf.max_worker; + if (num_childs > 0) { + int child = 0; + while (!child && !srv_shutdown) { + if (num_childs > 0) { + switch (fork()) { + case -1: + return -1; + case 0: + child = 1; + break; + default: + num_childs--; + break; + } + } else { + int status; + wait(&status); + num_childs++; + } + } + if (!child) return 0; + } +#endif + + /* main-loop */ + while (!srv_shutdown) { + int n; + size_t ndx; + time_t min_ts; + + if (handle_sig_hup) { + handler_t r; + + /* reset notification */ + handle_sig_hup = 0; + + + /* cycle logfiles */ + + switch(r = plugins_call_handle_sighup(srv)) { + case HANDLER_GO_ON: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "sd", "sighup-handler return with an error", r); + break; + } + + if (-1 == log_error_cycle(srv)) { + log_error_write(srv, __FILE__, __LINE__, "s", "cycling errorlog failed, dying"); + + return -1; + } + } + + if (handle_sig_alarm) { + /* a new second */ + +#ifdef USE_ALARM + /* reset notification */ + handle_sig_alarm = 0; +#endif + + /* get current time */ + min_ts = time(NULL); + + if (min_ts != srv->cur_ts) { + int cs = 0; + connections *conns = srv->conns; + handler_t r; + + switch(r = plugins_call_handle_trigger(srv)) { + case HANDLER_GO_ON: + break; + case HANDLER_ERROR: + log_error_write(srv, __FILE__, __LINE__, "s", "one of the triggers failed"); + break; + default: + log_error_write(srv, __FILE__, __LINE__, "d", r); + break; + } + + /* trigger waitpid */ + srv->cur_ts = min_ts; + + /** + * check all connections for timeouts + * + */ + for (ndx = 0; ndx < conns->used; ndx++) { + int changed = 0; + connection *con; + int t_diff; + + con = conns->ptr[ndx]; + + if (con->state == CON_STATE_READ || + con->state == CON_STATE_READ_POST) { + if (con->request_count == 1) { + if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) { + /* time - out */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed - read-timeout:", con->fd); +#endif + connection_set_state(srv, con, CON_STATE_ERROR); + changed = 1; + } + } else { + if (srv->cur_ts - con->read_idle_ts > con->conf.max_keep_alive_idle) { + /* time - out */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed - read-timeout:", con->fd); +#endif + connection_set_state(srv, con, CON_STATE_ERROR); + changed = 1; + } + } + } + + if ((con->state == CON_STATE_WRITE) && + (con->write_request_ts != 0)) { +#if 0 + if (srv->cur_ts - con->write_request_ts > 60) { + log_error_write(srv, __FILE__, __LINE__, "sdd", + "connection closed - pre-write-request-timeout:", con->fd, srv->cur_ts - con->write_request_ts); + } +#endif + + if (srv->cur_ts - con->write_request_ts > con->conf.max_write_idle) { + /* time - out */ +#if 1 + log_error_write(srv, __FILE__, __LINE__, "sd", + "connection closed - write-request-timeout:", con->fd); +#endif + connection_set_state(srv, con, CON_STATE_ERROR); + changed = 1; + } + } + /* we don't like div by zero */ + if (0 == (t_diff = srv->cur_ts - con->connection_start)) t_diff = 1; + + if (con->traffic_limit_reached && + ((con->bytes_written / t_diff) < con->conf.kbytes_per_second * 1024)) { + /* enable connection again */ + con->traffic_limit_reached = 0; + + changed = 1; + } + + if (changed) { + connection_state_machine(srv, con); + } + con->bytes_written_cur_second = 0; + *(con->conf.global_bytes_per_second_cnt_ptr) = 0; + +#if 0 + if (cs == 0) { + fprintf(stderr, "connection-state: "); + cs = 1; + } + + fprintf(stderr, "c[%d,%d]: %s ", + con->fd, + con->fcgi.fd, + connection_get_state(con->state)); +#endif + } + + if (cs == 1) fprintf(stderr, "\n"); + } + } + + /* handle out of fd condition */ + if (!srv->sockets_disabled && + srv->cur_fds + srv->want_fds > srv->max_fds * 0.9) { + + /* disable server-fds */ + + for (i = 0; i < srv->srv_sockets.used; i++) { + server_socket *srv_socket = srv->srv_sockets.ptr[i]; + fdevent_event_del(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd); + } + + log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets disabled, out-of-fds"); + + srv->sockets_disabled = 1; + } else if (srv->sockets_disabled && + srv->cur_fds + srv->want_fds < srv->max_fds * 0.8) { + + for (i = 0; i < srv->srv_sockets.used; i++) { + server_socket *srv_socket = srv->srv_sockets.ptr[i]; + fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN); + } + + log_error_write(srv, __FILE__, __LINE__, "s", "[note] sockets enabled, out-of-fds"); + + srv->sockets_disabled = 0; + } + + /* we still have some fds to share */ + if (srv->want_fds) { + /* check the fdwaitqueue for waiting fds */ + int free_fds = srv->max_fds - srv->cur_fds - 16; + connection *con; + + for (; free_fds > 0 && NULL != (con = fdwaitqueue_unshift(srv, srv->fdwaitqueue)); free_fds--) { + connection_state_machine(srv, con); + + srv->want_fds--; + } + } + + if ((n = fdevent_poll(srv->ev, 1000)) > 0) { + /* n is the number of events */ + int revents; + int fd_ndx; +#if 0 + if (n > 0) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "polls:", n); + } +#endif + fd_ndx = -1; + do { + fdevent_handler handler; + void *context; + handler_t r; + + fd_ndx = fdevent_event_next_fdndx (srv->ev, fd_ndx); + revents = fdevent_event_get_revent (srv->ev, fd_ndx); + fd = fdevent_event_get_fd (srv->ev, fd_ndx); + handler = fdevent_get_handler(srv->ev, fd); + context = fdevent_get_context(srv->ev, fd); + + /* connection_handle_fdevent needs a joblist_append */ +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sdd", + "event for", fd, revents); +#endif + switch (r = (*handler)(srv, context, revents)) { + case HANDLER_FINISHED: + case HANDLER_GO_ON: + case HANDLER_WAIT_FOR_EVENT: + break; + case HANDLER_ERROR: + /* should never happen */ + SEGFAULT(); + break; + default: + log_error_write(srv, __FILE__, __LINE__, "d", r); + break; + } + } while (--n > 0); + } else if (n < 0 && errno != EINTR) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "fdevent_poll failed:", + strerror(errno)); + } + + for (ndx = 0; ndx < srv->joblist->used; ndx++) { + connection *con = srv->joblist->ptr[ndx]; + handler_t r; + + connection_state_machine(srv, con); + + switch(r = plugins_call_handle_joblist(srv, con)) { + case HANDLER_FINISHED: + case HANDLER_GO_ON: + break; + default: + log_error_write(srv, __FILE__, __LINE__, "d", r); + break; + } + + con->in_joblist = 0; + } + + srv->joblist->used = 0; + } + + if (srv->srvconf.pid_file->used && + srv->srvconf.changeroot->used == 0) { + if (0 != unlink(srv->srvconf.pid_file->ptr)) { + if (errno != EPERM) { + log_error_write(srv, __FILE__, __LINE__, "sbds", + "unlink failed for:", + srv->srvconf.pid_file, + errno, + strerror(errno)); + } + } + } + + /* clean-up */ + log_error_close(srv); + network_close(srv); + plugins_free(srv); + server_free(srv); + + return 0; +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 00000000..bca2d525 --- /dev/null +++ b/src/server.h @@ -0,0 +1,17 @@ +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include "base.h" + +typedef struct { + char *key; + char *value; +} two_strings; + +typedef enum { CONFIG_UNSET, CONFIG_DOCUMENT_ROOT } config_var_t; + +int config_read(server *srv, const char *fn); +int config_set_defaults(server *srv); +buffer *config_get_value_buffer(server *srv, connection *con, config_var_t field); + +#endif diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 00000000..959edcc9 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,46 @@ +#ifndef _LIGHTTPD_SETTINGS_H_ +#define _LIGHTTPD_SETTINGS_H_ + +#define BV(x) (1 << x) + +#define INET_NTOP_CACHE_MAX 4 +#define FILE_CACHE_MAX 16 + +#define FCGI_RETRY_TIMEOUT (5 * 60) + +/** + * max size of a buffer which will just be reset + * to ->used = 0 instead of really freeing the buffer + * + * 64kB (no real reason, just a guess) + */ +#define BUFFER_MAX_REUSE_SIZE (4 * 1024) + +/** + * max size of the HTTP request header + * + * 32k should be enough for everything (just a guess) + * + */ +#define MAX_HTTP_REQUEST_HEADER (32 * 1024) + +typedef enum { HANDLER_UNSET, + HANDLER_GO_ON, + HANDLER_FINISHED, + HANDLER_COMEBACK, + HANDLER_WAIT_FOR_EVENT, + HANDLER_ERROR, + HANDLER_WAIT_FOR_FD +} handler_t; + + +/* we use it in a enum */ +#ifdef TRUE +#undef TRUE +#endif + +#ifdef FALSE +#undef FALSE +#endif + +#endif diff --git a/src/spawn-fcgi.c b/src/spawn-fcgi.c new file mode 100644 index 00000000..8be65cec --- /dev/null +++ b/src/spawn-fcgi.c @@ -0,0 +1,346 @@ +#include <sys/types.h> +#include <sys/time.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include "config.h" + +#ifdef HAVE_PWD_H +#include <grp.h> +#include <pwd.h> +#endif + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +#define FCGI_LISTENSOCK_FILENO 0 + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 +#endif + +#include "sys-socket.h" + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +/* for solaris 2.5 and netbsd 1.3.x */ +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +#ifdef HAVE_SYS_UN_H +int fcgi_spawn_connection(char *appPath, unsigned short port, const char *unixsocket, int child_count) { + int fcgi_fd; + int socket_type, status; + struct timeval tv = { 0, 100 * 1000 }; + + struct sockaddr_un fcgi_addr_un; + struct sockaddr_in fcgi_addr_in; + struct sockaddr *fcgi_addr; + + socklen_t servlen; + + if (child_count < 2) { + child_count = 5; + } + + if (child_count > 256) { + child_count = 256; + } + + + if (unixsocket) { + memset(&fcgi_addr, 0, sizeof(fcgi_addr)); + + fcgi_addr_un.sun_family = AF_UNIX; + strcpy(fcgi_addr_un.sun_path, unixsocket); + +#ifdef SUN_LEN + servlen = SUN_LEN(&fcgi_addr_un); +#else + /* stevens says: */ + servlen = strlen(fcgi_addr_un.sun_path) + sizeof(fcgi_addr_un.sun_family); +#endif + socket_type = AF_UNIX; + fcgi_addr = (struct sockaddr *) &fcgi_addr_un; + } else { + fcgi_addr_in.sin_family = AF_INET; + fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + fcgi_addr_in.sin_port = htons(port); + servlen = sizeof(fcgi_addr_in); + + socket_type = AF_INET; + fcgi_addr = (struct sockaddr *) &fcgi_addr_in; + } + + if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + fprintf(stderr, "%s.%d\n", + __FILE__, __LINE__); + return -1; + } + + if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) { + /* server is not up, spawn in */ + pid_t child; + + if (unixsocket) unlink(unixsocket); + + close(fcgi_fd); + + /* reopen socket */ + if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + fprintf(stderr, "%s.%d\n", + __FILE__, __LINE__); + return -1; + } + + /* create socket */ + if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) { + fprintf(stderr, "%s.%d: bind failed: %s\n", + __FILE__, __LINE__, + strerror(errno)); + return -1; + } + + if (-1 == listen(fcgi_fd, 1024)) { + fprintf(stderr, "%s.%d: fd = -1\n", + __FILE__, __LINE__); + return -1; + } + + switch ((child = fork())) { + case 0: { + char cgi_childs[64]; + + int i = 0; + + /* is save as we limit to 256 childs */ + sprintf(cgi_childs, "PHP_FCGI_CHILDREN=%d", child_count); + + if(fcgi_fd != FCGI_LISTENSOCK_FILENO) { + close(FCGI_LISTENSOCK_FILENO); + dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO); + close(fcgi_fd); + } + + /* we don't need the client socket */ + for (i = 3; i < 256; i++) { + close(i); + } + + /* create environment */ + + putenv(cgi_childs); + + /* exec the cgi */ + execl("/bin/sh", "sh", "-c", appPath, NULL); + + exit(errno); + + break; + } + case -1: + /* error */ + break; + default: + /* father */ + + /* wait */ + select(0, NULL, NULL, NULL, &tv); + + switch (waitpid(child, &status, WNOHANG)) { + case 0: + fprintf(stderr, "%s.%d: child spawned successfully: PID: %d\n", + __FILE__, __LINE__, + child); + break; + case -1: + break; + default: + if (WIFEXITED(status)) { + fprintf(stderr, "%s.%d: child exited with: %d, %s\n", + __FILE__, __LINE__, + WEXITSTATUS(status), strerror(WEXITSTATUS(status))); + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "%s.%d: child signaled: %d\n", + __FILE__, __LINE__, + WTERMSIG(status)); + } else { + fprintf(stderr, "%s.%d: child died somehow: %d\n", + __FILE__, __LINE__, + status); + } + } + + break; + } + } else { + fprintf(stderr, "%s.%d: socket is already used, can't spawn\n", + __FILE__, __LINE__); + return -1; + } + + close(fcgi_fd); + + return 0; +} + + +void show_version () { + char *b = "spawn-fcgi" "-" VERSION \ +" - spawns fastcgi processes\n" \ +"Build-Date: " __DATE__ " " __TIME__ "\n"; +; + write(1, b, strlen(b)); +} + +void show_help () { + char *b = "spawn-fcgi" "-" VERSION \ +" - spawns fastcgi processes\n" \ +"usage:\n" \ +" -f <fcgiapp> filename of the fcgi-application\n" \ +" -p <port> bind to tcp-port\n" \ +" -s <path> bind to unix-domain socket\n" \ +" -C <childs> (PHP only) numbers of childs to spawn (default 5)\n" \ +" -v show version\n" \ +" -h show this help\n" \ +"(root only)\n" \ +" -c <dir> chroot to directory\n" \ +" -u <user> change to user-id\n" \ +" -g <group> change to group-id\n" \ +; + write(1, b, strlen(b)); +} + + +int main(int argc, char **argv) { + char *fcgi_app = NULL, *changeroot = NULL, *username = NULL, *groupname = NULL, *unixsocket = NULL; + unsigned short port = 0; + int child_count = 5; + int i_am_root, o; + + i_am_root = (getuid() == 0); + + while(-1 != (o = getopt(argc, argv, "c:f:g:hp:u:vC:s:"))) { + switch(o) { + case 'f': fcgi_app = optarg; break; + case 'p': port = strtol(optarg, NULL, 10);/* port */ break; + case 'C': child_count = strtol(optarg, NULL, 10);/* */ break; + case 's': unixsocket = optarg; /* unix-domain socket */ break; + case 'c': if (i_am_root) { changeroot = optarg; }/* chroot() */ break; + case 'u': if (i_am_root) { username = optarg; } /* set user */ break; + case 'g': if (i_am_root) { groupname = optarg; } /* set group */ break; + case 'v': show_version(); return 0; + case 'h': show_help(); return 0; + default: + show_help(); + return -1; + } + } + + if (fcgi_app == NULL || (port == 0 && unixsocket == NULL)) { + show_help(); + return -1; + } + + if (unixsocket && port) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "either a unix domain socket or a tcp-port, but not both\n"); + + return -1; + } + + if (unixsocket && strlen(unixsocket) > UNIX_PATH_MAX - 1) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "path of the unix socket is too long\n"); + + return -1; + } + + /* UID handling */ + if (!i_am_root && (geteuid() == 0 || getegid() == 0)) { + /* we are setuid-root */ + + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "Are you nuts ? Don't apply a SUID bit to this binary\n"); + + return -1; + } + + if (i_am_root) { + struct group *grp = NULL; + struct passwd *pwd = NULL; + + /* set user and group */ + + if (username) { + if (NULL == (pwd = getpwnam(username))) { + fprintf(stderr, "%s.%d: %s, %s\n", + __FILE__, __LINE__, + "can't find username", username); + return -1; + } + + if (pwd->pw_uid == 0) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "I will not set uid to 0\n"); + return -1; + } + } + + if (groupname) { + if (NULL == (grp = getgrnam(groupname))) { + fprintf(stderr, "%s.%d: %s %s\n", + __FILE__, __LINE__, + "can't find groupname", + groupname); + return -1; + } + if (grp->gr_gid == 0) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "I will not set gid to 0\n"); + return -1; + } + } + + if (changeroot) { + if (-1 == chroot(changeroot)) { + fprintf(stderr, "%s.%d: %s %s\n", + __FILE__, __LINE__, + "chroot failed: ", strerror(errno)); + return -1; + } + if (-1 == chdir("/")) { + fprintf(stderr, "%s.%d: %s %s\n", + __FILE__, __LINE__, + "chdir failed: ", strerror(errno)); + return -1; + } + } + + /* drop root privs */ + if (groupname) { + setgid(grp->gr_gid); + setgroups(0, NULL); + } + if (username) setuid(pwd->pw_uid); + } + + return fcgi_spawn_connection(fcgi_app, port, unixsocket, child_count); +} +#else +int main() { + return -1; +} +#endif diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 00000000..ec228d85 --- /dev/null +++ b/src/stream.c @@ -0,0 +1,100 @@ +#include <sys/types.h> +#include <sys/stat.h> + +#include <unistd.h> +#include <fcntl.h> + +#include "stream.h" +#include "config.h" + +#include "sys-mmap.h" + + +int stream_open(stream *f, buffer *fn) { + struct stat st; +#ifdef HAVE_MMAP + int fd; +#elif defined __WIN32 + HANDLE *fh, *mh; + void *p; +#endif + + + if (-1 == stat(fn->ptr, &st)) { + return -1; + } + + f->size = st.st_size; + +#ifdef HAVE_MMAP + if (-1 == (fd = open(fn->ptr, O_RDONLY))) { + return -1; + } + + f->start = mmap(0, f->size, PROT_READ, MAP_SHARED, fd, 0); + + close(fd); + + if (MAP_FAILED == f->start) { + return -1; + } + +#elif defined __WIN32 + fh = CreateFile(fn->ptr, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY, + NULL); + + if (!fh) return -1; + + mh = CreateFileMapping( fh, + NULL, + PAGE_READONLY, + (sizeof(off_t) > 4) ? f->size >> 32 : 0, + f->size & 0xffffffff, + NULL); + + if (!mh) { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + return -1; + } + + p = MapViewOfFile(mh, + FILE_MAP_READ, + 0, + 0, + 0); + CloseHandle(mh); + CloseHandle(fh); + + f->start = p; +#else +# error no mmap found +#endif + + return 0; +} + +int stream_close(stream *f) { +#ifdef HAVE_MMAP + if (f->start) munmap(f->start, f->size); +#elif defined(__WIN32) + if (f->start) UnmapViewOfFile(f->start); +#endif + + f->start = NULL; + + return 0; +} diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 00000000..d4c9049b --- /dev/null +++ b/src/stream.h @@ -0,0 +1,14 @@ +#ifndef _STREAM_H_ +#define _STREAM_H_ + +#include "buffer.h" + +typedef struct { + char *start; + off_t size; +} stream; + +int stream_open(stream *f, buffer *fn); +int stream_close(stream *f); + +#endif diff --git a/src/sys-mmap.h b/src/sys-mmap.h new file mode 100644 index 00000000..94aaa19b --- /dev/null +++ b/src/sys-mmap.h @@ -0,0 +1,24 @@ +#ifndef WIN32_MMAP_H +#define WIN32_MMAP_H + +#ifdef __WIN32 + +#define MAP_FAILED -1 +#define PROT_SHARED 0 +#define MAP_SHARED 0 +#define PROT_READ 0 + +#define mmap(a, b, c, d, e, f) (-1) +#define munmap(a, b) (-1) + +#include <windows.h> + +#else +#include <sys/mman.h> + +#ifndef MAP_FAILED +#define MAP_FAILED -1 +#endif +#endif + +#endif diff --git a/src/sys-socket.h b/src/sys-socket.h new file mode 100644 index 00000000..cc6e649b --- /dev/null +++ b/src/sys-socket.h @@ -0,0 +1,24 @@ +#ifndef WIN32_SOCKET_H +#define WIN32_SOCKET_H + +#ifdef __WIN32 + +#include <winsock2.h> + +#define ECONNRESET WSAECONNRESET +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#define ioctl ioctlsocket +#define hstrerror(x) "" +#else +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/un.h> +#include <arpa/inet.h> + +#include <netdb.h> +#endif + +#endif diff --git a/tests/.cvsignore b/tests/.cvsignore new file mode 100644 index 00000000..302e477d --- /dev/null +++ b/tests/.cvsignore @@ -0,0 +1,5 @@ +Makefile.in +Makefile +.deps +.libs +fcgi-auth diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 00000000..859ab63a --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,128 @@ +# lighttpd.conf and conformance.pl expect this directory +testdir=/tmp/lighttpd/ + +check_PROGRAMS=fcgi-auth + +fcgi_auth_SOURCES=fcgi-auth.c +fcgi_auth_LDADD=-lfcgi + +TESTS=\ +prepare.sh \ +basic-01.sh \ +basic-02.sh \ +basic-03.sh \ +basic-05.sh \ +basic-06.sh \ +basic-07.sh \ +basic-08.sh \ +basic-09.sh \ +basic-10.sh \ +basic-11.sh \ +bug-urldecode-00.sh \ +bug-03.sh \ +bug-06.sh \ +bug-12.sh \ +bug-14.sh \ +bug-15.sh \ +bug-15-2.sh \ +bug-15-3.sh \ +broken-header-01.sh \ +content-length-01.sh \ +content-length-02.sh \ +content-length-03.sh \ +content-length-04.sh \ +content-length-05.sh \ +head-01.sh \ +post-01.sh \ +post-02.sh \ +host-01.sh \ +host-02.sh \ +host-03.sh \ +host-04.sh \ +host-05.sh \ +http11-01.sh \ +http11-02.sh \ +http11-03.sh \ +missing-01.sh \ +missing-02.sh \ +large-header-01.sh \ +accessdeny-01.sh \ +cgi-01.sh \ +cgi-02.sh \ +cgi-03.sh \ +compress-01.sh \ +compress-02.sh \ +compress-03.sh \ +compress-04.sh \ +fastcgi-01.sh \ +fastcgi-02.sh \ +fastcgi-03.sh \ +fastcgi-04.sh \ +fastcgi-05.sh \ +fastcgi-06.sh \ +fastcgi-07.sh \ +fastcgi-08.sh \ +fastcgi-09.sh \ +fastcgi-10.sh \ +fastcgi-11.sh \ +fastcgi-12.sh \ +fastcgi-13.sh \ +auth-01.sh \ +auth-02.sh \ +auth-03.sh \ +content-01.sh \ +content-02.sh \ +content-03.sh \ +content-04.sh \ +leak-01.sh \ +leak-02.sh \ +leak-03.sh \ +leak-04.sh \ +leak-05.sh \ +leak-06.sh \ +leak-07.sh \ +leak-08.sh \ +leak-09.sh \ +leak-10.sh \ +leak-11.sh \ +leak-12.sh \ +leak-13.sh \ +leak-14.sh \ +leak-15.sh \ +leak-16.sh \ +leak-17.sh \ +redirect-01.sh \ +redirect-02.sh \ +redirect-03.sh \ +pathinfo-01.sh \ +pathinfo-02.sh \ +broken-key-01.sh \ +broken-key-02.sh \ +broken-key-03.sh \ +broken-key-04.sh \ +continue-01.sh \ +cleanup.sh + +CONFS=fastcgi-10.conf \ + fastcgi-11.conf \ + fastcgi-12.conf \ + fastcgi-13.conf \ + bug-06.conf \ + bug-12.conf + +TESTS_ENVIRONMENT=$(srcdir)/wrapper.sh $(srcdir) $(top_builddir) + +EXTRA_DIST=conformance.pl wrapper.sh testbase.sh lighttpd.conf \ + lighttpd.user \ + $(CONFS) \ + $(TESTS) +SUBDIRS=docroot + +leak-check: + for i in $(TESTS); do \ + $(srcdir)/$$i; \ + echo $$?; \ + done + +clean-local: + rm -f *.out diff --git a/tests/accessdeny-01.sh b/tests/accessdeny-01.sh new file mode 100755 index 00000000..e9a80bc9 --- /dev/null +++ b/tests/accessdeny-01.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +deny request for index.html~ +GET /index.html~ HTTP/1.0 + +Status: 403 +EOF + +run_test diff --git a/tests/auth-01.sh b/tests/auth-01.sh new file mode 100755 index 00000000..59d5e7cd --- /dev/null +++ b/tests/auth-01.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +auth required, no token sent +GET /server-status HTTP/1.0 + +Status: 401 +EOF + +run_test diff --git a/tests/auth-02.sh b/tests/auth-02.sh new file mode 100755 index 00000000..ad8241b7 --- /dev/null +++ b/tests/auth-02.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +auth required, wrong token +GET /server-status HTTP/1.0 +Authorization: Basic amFuOmphb + +Status: 401 +EOF + +run_test diff --git a/tests/auth-03.sh b/tests/auth-03.sh new file mode 100755 index 00000000..906fcfb4 --- /dev/null +++ b/tests/auth-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +auth required, good token +GET /server-config HTTP/1.0 +Authorization: Basic amFuOmphbg== + +Status: 200 +EOF + +run_test diff --git a/tests/basic-01.sh b/tests/basic-01.sh new file mode 100755 index 00000000..1d24fd82 --- /dev/null +++ b/tests/basic-01.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +valid request +GET / HTTP/1.0 + +Status: 200 +EOF + +run_test diff --git a/tests/basic-02.sh b/tests/basic-02.sh new file mode 100755 index 00000000..671111c0 --- /dev/null +++ b/tests/basic-02.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Missing Protocol +GET / + +Status: 400 +Protocol: HTTP/0.9 +EOF + +run_test + diff --git a/tests/basic-03.sh b/tests/basic-03.sh new file mode 100755 index 00000000..438ca04e --- /dev/null +++ b/tests/basic-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Missing Protocol + Unknown Method +ABC / + +Status: 400 +Protocol: HTTP/0.9 +EOF + +run_test diff --git a/tests/basic-05.sh b/tests/basic-05.sh new file mode 100755 index 00000000..4c520d43 --- /dev/null +++ b/tests/basic-05.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Broken Request Header +ASd + +Status: 400 +Protocol: HTTP/0.9 +EOF + +run_test diff --git a/tests/basic-06.sh b/tests/basic-06.sh new file mode 100755 index 00000000..b51b8c97 --- /dev/null +++ b/tests/basic-06.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Unknown Method +ABC / HTTP/1.0 + +Status: 501 +EOF + +run_test diff --git a/tests/basic-07.sh b/tests/basic-07.sh new file mode 100755 index 00000000..d70b78c1 --- /dev/null +++ b/tests/basic-07.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +## +# using a higher protocol is always allowed as we can +# downgrade the protocol on our own in the response +# + + +cat > $TMPFILE <<EOF +Protocoll == HTTP/1.3 +GET / HTTP/1.3 +Host: testbase.home.kneschke.de + +Status: 505 +EOF + +run_test diff --git a/tests/basic-08.sh b/tests/basic-08.sh new file mode 100755 index 00000000..a6690499 --- /dev/null +++ b/tests/basic-08.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +HTTP/1.1, with missing host +GET / HTTP/1.1 + +Status: 400 +EOF + +run_test diff --git a/tests/basic-09.sh b/tests/basic-09.sh new file mode 100755 index 00000000..3231a5b4 --- /dev/null +++ b/tests/basic-09.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +HTTP/1.0, host in URI +GET http://www.yahoo.com/ HTTP/1.0 + +Status: 200 +EOF + +run_test diff --git a/tests/basic-10.sh b/tests/basic-10.sh new file mode 100755 index 00000000..3b1bf948 --- /dev/null +++ b/tests/basic-10.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +## +# apache excepts broken request headers +# +# + +cat > $TMPFILE <<EOF +broken requestline (4 fields) +GET http://www.yahoo.com/ HTTP/1.0 jsdh + +Status: 400 +EOF + +run_test diff --git a/tests/basic-11.sh b/tests/basic-11.sh new file mode 100755 index 00000000..05d19fdf --- /dev/null +++ b/tests/basic-11.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +## +# +# apache sends 400 +# + +cat > $TMPFILE <<EOF +Docroot protection +GET /../ HTTP/1.0 + +Status: 200 +EOF + +run_test diff --git a/tests/broken-header-01.sh b/tests/broken-header-01.sh new file mode 100755 index 00000000..6ccafb7f --- /dev/null +++ b/tests/broken-header-01.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Header appears twice +GET / HTTP/1.0 +Foo: foo +Foo: foo + +Status: 200 +EOF + +run_test diff --git a/tests/broken-key-01.sh b/tests/broken-key-01.sh new file mode 100755 index 00000000..abbd60c6 --- /dev/null +++ b/tests/broken-key-01.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Broken Key-Value pairs +GET / HTTP/1.0 +ABC : jsajfsfdg + +Status: 200 +EOF + +run_test diff --git a/tests/broken-key-02.sh b/tests/broken-key-02.sh new file mode 100755 index 00000000..dddae9dd --- /dev/null +++ b/tests/broken-key-02.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Broken Key-Value pairs +GET / HTTP/1.0 +ABC a: jsajfsfdg + +Status: 400 +EOF + +run_test diff --git a/tests/broken-key-03.sh b/tests/broken-key-03.sh new file mode 100755 index 00000000..af5c0b6c --- /dev/null +++ b/tests/broken-key-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Broken Key-Value pairs +GET / HTTP/1.0 +ABC:jsajfsfdg + +Status: 200 +EOF + +run_test diff --git a/tests/broken-key-04.sh b/tests/broken-key-04.sh new file mode 100755 index 00000000..38d91d7d --- /dev/null +++ b/tests/broken-key-04.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Broken Key-Value pairs +GET / HTTP/1.0 +ABC : jsajfsfdg + kde.org + +Status: 200 +EOF + +run_test diff --git a/tests/bug-03.sh b/tests/bug-03.sh new file mode 100755 index 00000000..c46e6735 --- /dev/null +++ b/tests/bug-03.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "FastCGI PHPinfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +PHP_SELF + indexfile +GET /indexfile/ HTTP/1.0 +Host: www.example.org + +Status: 200 +Content: /indexfile/index.php +EOF + +run_test + diff --git a/tests/bug-06.conf b/tests/bug-06.conf new file mode 100644 index 00000000..b286f48d --- /dev/null +++ b/tests/bug-06.conf @@ -0,0 +1,164 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +fastcgi.debug = 1 + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_setenv", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", + "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_accesslog" ) + +server.indexfiles = ( "index.html", + "index.htm", "default.htm", "index.php" ) + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +setenv.add-environment = ( "TRAC_ENV" => "foo") +setenv.add-request-header = ( "FOO" => "foo") +setenv.add-response-header = ( "BAR" => "foo") + +fastcgi.debug = 0 +fastcgi.server = ( ".php" => ( + "grisu" => ( + "host" => "192.168.0.2", + "port" => 1026, +# "mode" => "authorizer", +# "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/", + ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + + + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +simple-vhost.document-root = "pages" +simple-vhost.server-root = "/tmp/lighttpd/servers/" +simple-vhost.default-host = "www.example.org" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/bug-06.sh b/tests/bug-06.sh new file mode 100755 index 00000000..336c8919 --- /dev/null +++ b/tests/bug-06.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "FastCGI PHPinfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +PHP_SELF + last indexfile +GET /indexfile/ HTTP/1.0 +Host: www.example.org + +Status: 200 +Content: /indexfile/index.php +EOF + +run_test + diff --git a/tests/bug-12.conf b/tests/bug-12.conf new file mode 100644 index 00000000..cbac1b3f --- /dev/null +++ b/tests/bug-12.conf @@ -0,0 +1,166 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +fastcgi.debug = 1 + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_setenv", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", + "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_accesslog" ) + +server.indexfiles = ( "index.html", + "index.htm", "default.htm", "index.php" ) + +server.error-handler-404 = "/indexfile/return-404.php" + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +setenv.add-environment = ( "TRAC_ENV" => "foo") +setenv.add-request-header = ( "FOO" => "foo") +setenv.add-response-header = ( "BAR" => "foo") + +fastcgi.debug = 0 +fastcgi.server = ( ".php" => ( + "grisu" => ( + "host" => "192.168.0.2", + "port" => 1026, +# "mode" => "authorizer", +# "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/", + ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + + + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +simple-vhost.document-root = "pages" +simple-vhost.server-root = "/tmp/lighttpd/servers/" +simple-vhost.default-host = "www.example.org" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/bug-12.sh b/tests/bug-12.sh new file mode 100755 index 00000000..7bdb0690 --- /dev/null +++ b/tests/bug-12.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "FastCGI PHPinfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +FastCGI + errorhandler +POST /indexfile/abc HTTP/1.0 +Host: www.example.org +Content-Length: 0 + +Status: 404 +Content: /indexfile/return-404.php +EOF + +run_test + diff --git a/tests/bug-14.sh b/tests/bug-14.sh new file mode 100755 index 00000000..505f15ec --- /dev/null +++ b/tests/bug-14.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +CGI + NPH +GET /nph-status.pl HTTP/1.0 +Host: www.example.org + +Status: 200 +EOF + +run_test + diff --git a/tests/bug-15-2.sh b/tests/bug-15-2.sh new file mode 100755 index 00000000..371dbc80 --- /dev/null +++ b/tests/bug-15-2.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if id weigon > /dev/null; then + echo -n +else + printf "%-40s" "userdir" + exit 77 +fi + + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +userdir for ~weigon + redirect +GET /~weigon HTTP/1.0 +Host: www.example.org + +Status: 301 +Location: http://www.example.org/~weigon/ +EOF + +run_test + diff --git a/tests/bug-15-3.sh b/tests/bug-15-3.sh new file mode 100755 index 00000000..b51810f7 --- /dev/null +++ b/tests/bug-15-3.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +if id weigon > /dev/null; then + echo -n +else + printf "%-40s" "userdir" + exit 77 +fi + + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +userdir for ~weigon + redirect +GET /~weigon HTTP/1.0 + +Status: 301 +Location: http://localhost:2048/~weigon/ +EOF + +run_test + diff --git a/tests/bug-15.sh b/tests/bug-15.sh new file mode 100755 index 00000000..eb33eb35 --- /dev/null +++ b/tests/bug-15.sh @@ -0,0 +1,27 @@ +#!/bin/sh + + +if id weigon > /dev/null; then + echo -n +else + printf "%-40s" "userdir" + exit 77 +fi + + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +userdir for ~weigon +GET /~weigon/ HTTP/1.0 +Host: www.example.org + +Status: 200 +EOF + +run_test + diff --git a/tests/bug-urldecode-00.sh b/tests/bug-urldecode-00.sh new file mode 100755 index 00000000..4f34e841 --- /dev/null +++ b/tests/bug-urldecode-00.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Check that %00 is decoded correctly +GET /%00 HTTP/1.0 +Foo: foo +Foo: foo + +Status: 404 +EOF + +run_test diff --git a/tests/cgi-01.sh b/tests/cgi-01.sh new file mode 100755 index 00000000..3406a3ae --- /dev/null +++ b/tests/cgi-01.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +CGI +GET /cgi.pl HTTP/1.0 + +Status: 200 +EOF + +run_test diff --git a/tests/cgi-02.sh b/tests/cgi-02.sh new file mode 100755 index 00000000..3c0d3f0f --- /dev/null +++ b/tests/cgi-02.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +CGI - SCRIPT_NAME (+pathinfo) +GET /cgi.pl/foo HTTP/1.0 + +Status: 200 +Content: /cgi.pl +EOF + +run_test diff --git a/tests/cgi-03.sh b/tests/cgi-03.sh new file mode 100755 index 00000000..a9b81186 --- /dev/null +++ b/tests/cgi-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +CGI + pathinfo +GET /cgi-pathinfo.pl/foo HTTP/1.0 + +Status: 200 +Content: /foo +EOF + +run_test diff --git a/tests/cleanup.sh b/tests/cleanup.sh new file mode 100755 index 00000000..88e241f7 --- /dev/null +++ b/tests/cleanup.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +tmpdir=/tmp/lighttpd + +if test x$srcdir = x; then + srcdir=. +fi + +# create test-framework +# rm -rf $tmpdir + +printf "%-40s" "cleaning up" + +exit 0 diff --git a/tests/compress-01.sh b/tests/compress-01.sh new file mode 100755 index 00000000..4fcfc77c --- /dev/null +++ b/tests/compress-01.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Compression - deflate +GET /index.html HTTP/1.0 +Accept-Encoding: deflate + +Status: 200 +MUST: Vary +EOF + +run_test diff --git a/tests/compress-02.sh b/tests/compress-02.sh new file mode 100755 index 00000000..ae91a9bc --- /dev/null +++ b/tests/compress-02.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Compression - deflate +GET /index.html HTTP/1.0 +Accept-Encoding: deflate + +Status: 200 +Content-Length: 1288 +MUST: Vary Content-Encoding +EOF + +run_test diff --git a/tests/compress-03.sh b/tests/compress-03.sh new file mode 100755 index 00000000..830e02f9 --- /dev/null +++ b/tests/compress-03.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Compression - gzip +GET /index.html HTTP/1.0 +Accept-Encoding: gzip + +Status: 200 +MUST: Vary Content-Encoding +EOF + +run_test diff --git a/tests/compress-04.sh b/tests/compress-04.sh new file mode 100755 index 00000000..c08f3975 --- /dev/null +++ b/tests/compress-04.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Compression - gzip +GET /index.txt HTTP/1.0 +Accept-Encoding: gzip, deflate +Host: www.example.org + +Status: 200 +MIGHT: Content-Encoding Vary +EOF + +run_test diff --git a/tests/conformance.pl b/tests/conformance.pl new file mode 100755 index 00000000..7f754975 --- /dev/null +++ b/tests/conformance.pl @@ -0,0 +1,164 @@ +#! /usr/bin/perl -w + +use strict; +use IO::Socket; + +my $EOL = "\015\012"; +my $BLANK = $EOL x 2; + + +my @f = <STDIN>; + +# drop first line +my $headline = shift @f; +chomp $headline; +printf STDERR "%-40s", $headline." "; + + +my $remote = + IO::Socket::INET->new(Proto => "tcp", + PeerAddr => "127.0.0.1", + PeerPort => $#ARGV == 0 ? "1025" : "2048") + or die "cannot connect to remote host"; + +$remote->autoflush(1); + +my %y; +my $m = 0; +my $line = 0; +my $method; +foreach(@f) { + if (/^$/) { + $m = 1; + next; + } + + chomp; + if ($m == 0) { + # header line + # + if ($line++ == 0) { + ($method = $_ ) =~ s/ .*//; + } + print $remote $_.$EOL; + } else { + my ($key, $value) = split /: /, $_; + + $y{$key} = $value; + } +} +print $remote $EOL; + +my $ln = 0; +my $error = 0; +my $con_len = -1; +my $body = ""; +$m = 0; + +my %header; +while(<$remote>) { + $ln++; + +# print STDERR $_; + + if ($ln == 1) { + if (/^HTTP/) { + my ($proto, $status, $text) = split / /, $_, 3; + if (defined $y{"Status"}) { + if ($status ne $y{"Status"}) { + $error = 1; + print STDERR "E: wrong Status code - "; + } + } + } elsif ($y{"Protocol"} eq "HTTP/0.9") { + # we expected HTTP/0.9 or Bad Protocol + $m = 1; + } else { + $error = 1; + print STDERR "E: broken something - "; + } + } elsif ($m == 0) { + # response header + my ($key, $value) = split /: /, $_; + + if (not /^\r$/) { + ($header{$key} = $value) =~ s/\r\n$//; + } + } + + # grep for content-length + if (/^Content-Length: ([0-9]+)\r$/) { + $con_len = $1; + } + + if ($m == 1) { + $body .= $_; + } + + if (/^\r$/) { + $m = 1; + } + + print $_; + + if ($m == 1 && (length($body) == $con_len)) { +# print STDERR length($body)." - ".$con_len."\n"; + last; + } +} + +close $remote; + +if ($con_len != -1 && $method ne "HEAD" && $m == 1 && (length($body) != $con_len)) { + $error = 1; + print STDERR "E: wrong content-length - "; +} + +# check the MUST header + +if (defined $y{"MUST"}) { + foreach (split / /, $y{"MUST"}) { + if (not defined $header{$_}) { + $error = 1; + print STDERR "E: MUST missing - "; + } + } +} +my $might = 0; +if (defined $y{"MIGHT"}) { + foreach (split / /, $y{"MIGHT"}) { + if (not defined $header{$_}) { + $might = 1; + } + } +} + +if (defined $y{"Content"}) { + if ($body ne $y{"Content"}) { + $error = 1; + print STDERR "E: Content doesn't match - "; + } +} + +foreach (keys %y) { + next if /^MIGHT$/; + next if /^MUST$/; + next if /^Status$/; + next if /^Protocol$/; + next if /^Content$/; + + if ((not defined $header{$_}) || + ($header{$_} ne $y{$_})) { + $error = 1; + print STDERR "E: headerline missing - "; + } +} + +if ($error) { + exit 1; +} elsif ($might) { + exit 77; +} else { + exit 0; +} + diff --git a/tests/content-01.sh b/tests/content-01.sh new file mode 100755 index 00000000..de4b6089 --- /dev/null +++ b/tests/content-01.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +docroot=/tmp/lighttpd/servers/123.example.org/pages/ +reqfile=12345.txt +test -d $docroot || exit 77 + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Type +GET /$reqfile HTTP/1.0 +Host: 123.example.org + +Content-Type: text/plain +EOF + +run_test_script + +if test x$exitcode = x0; then + + if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then + a=a + else + exitcode=-1 + fi +fi + +run_test_exit diff --git a/tests/content-02.sh b/tests/content-02.sh new file mode 100755 index 00000000..6974e5f3 --- /dev/null +++ b/tests/content-02.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +docroot=/tmp/lighttpd/servers/123.example.org/pages/ +reqfile=12345.html +test -d $docroot || exit 77 + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Type +GET /$reqfile HTTP/1.0 +Host: 123.example.org + +Content-Type: text/html +EOF + +run_test_script + +if test x$exitcode = x0; then + + if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then + a=a + else + exitcode=-1 + fi +fi + +run_test_exit diff --git a/tests/content-03.sh b/tests/content-03.sh new file mode 100755 index 00000000..3cfdbd39 --- /dev/null +++ b/tests/content-03.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +docroot=/tmp/lighttpd/servers/123.example.org/pages/ +reqfile=dummyfile.bla +test -d $docroot || exit 77 + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Type +GET /$reqfile HTTP/1.0 +Host: 123.example.org + +Content-Type: application/octet-stream +EOF + +run_test_script + +if test x$exitcode = x0; then + + if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then + a=a + else + exitcode=-1 + fi +fi + +run_test_exit diff --git a/tests/content-04.sh b/tests/content-04.sh new file mode 100755 index 00000000..2c2e01e7 --- /dev/null +++ b/tests/content-04.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Getting PHP code" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +docroot=/tmp/lighttpd/servers/123.example.org/pages/ +reqfile=phpinfo.php +test -d $docroot || exit 77 + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Type +GET /$reqfile HTTP/1.0 +Host: 123.example.org + +EOF + +run_test_script + +if test x$exitcode = x0; then + # got the source of the php-file + if cat $NAME.out | sed '1,/^.$/d' | cmp - $docroot/$reqfile; then + exitcode=-1 + fi > /dev/null +fi + +run_test_exit diff --git a/tests/content-length-01.sh b/tests/content-length-01.sh new file mode 100755 index 00000000..1d3e4b88 --- /dev/null +++ b/tests/content-length-01.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Length, HTML +GET /12345.html HTTP/1.0 +Host: 123.example.org + +Status: 200 +Content-Length: 6 +EOF + +run_test diff --git a/tests/content-length-02.sh b/tests/content-length-02.sh new file mode 100755 index 00000000..a271b224 --- /dev/null +++ b/tests/content-length-02.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Length, HTML +GET /12345.txt HTTP/1.0 +Host: 123.example.org + +Status: 200 +Content-Length: 6 +EOF + +run_test diff --git a/tests/content-length-03.sh b/tests/content-length-03.sh new file mode 100755 index 00000000..547dc6ee --- /dev/null +++ b/tests/content-length-03.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Length < 0 +POST /12345.txt HTTP/1.0 +Host: 123.example.org +Content-Length: -473 + +Status: 400 +EOF + +run_test diff --git a/tests/content-length-04.sh b/tests/content-length-04.sh new file mode 100755 index 00000000..3b57b12d --- /dev/null +++ b/tests/content-length-04.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Length < 0 +POST /12345.txt HTTP/1.0 +Host: 123.example.org +Content-Length: 2147483648 + +Status: 413 +EOF + +run_test diff --git a/tests/content-length-05.sh b/tests/content-length-05.sh new file mode 100755 index 00000000..f528c78e --- /dev/null +++ b/tests/content-length-05.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Content-Length < 0 +POST /12345.txt HTTP/1.0 +Host: 123.example.org +Content-Length: + +Status: 411 +EOF + +run_test diff --git a/tests/continue-01.sh b/tests/continue-01.sh new file mode 100755 index 00000000..26e090c1 --- /dev/null +++ b/tests/continue-01.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Continue Handling +GET / HTTP/1.1 +Connection: Close +Expect: 100-continue + +Status: 417 +EOF + +run_test diff --git a/tests/docroot/123/12345.html b/tests/docroot/123/12345.html new file mode 100644 index 00000000..e56e15bb --- /dev/null +++ b/tests/docroot/123/12345.html @@ -0,0 +1 @@ +12345 diff --git a/tests/docroot/123/12345.txt b/tests/docroot/123/12345.txt new file mode 100644 index 00000000..e56e15bb --- /dev/null +++ b/tests/docroot/123/12345.txt @@ -0,0 +1 @@ +12345 diff --git a/tests/docroot/123/Makefile.am b/tests/docroot/123/Makefile.am new file mode 100644 index 00000000..64712d7a --- /dev/null +++ b/tests/docroot/123/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST=12345.html 12345.txt dummyfile.bla phpinfo.php diff --git a/tests/docroot/123/dummyfile.bla b/tests/docroot/123/dummyfile.bla new file mode 100644 index 00000000..024416aa --- /dev/null +++ b/tests/docroot/123/dummyfile.bla @@ -0,0 +1 @@ +ߤ¨*rð.üGC䳜‰nÑ$ê.ª!bgX¥8{©³[)ûx¸”žÕTª¨ç¸jã+ ò~ Ä/V¼¼UŒúCIvn{ÀXKïFŽÝÍܸ¥íyÕ Â(bE¨¼çD‡>ŒÏÛ†[Q ²¦´ :±ô=¬H+Ç>{ó½fKÝÆaO€ûžod¹ šï$ÜÍÎUïayø«Üç&QWÑ$¡§Œšóqìu~&o^Û5ˆ&….¶ë˜04gšt€.nä¤Ò…O28%W@;8Ì&4
k9¸h/«Ú™Zø£™m°ÅÁ[ä´ß¿0õìL@úÖÐè'^øjfð–‹bJ ¼ðí±cAÖ/i‹>.§ÆÔPxüÛ–ÝiÐ2„HîÅʇḻ48ÿËÄ'ZkÎàÀþ¼&¡!?@Fü-o©j&€Âÿ}õ¹`1Í«!ϸ¤†
\ No newline at end of file diff --git a/tests/docroot/123/phpinfo.php b/tests/docroot/123/phpinfo.php new file mode 100644 index 00000000..147cebcd --- /dev/null +++ b/tests/docroot/123/phpinfo.php @@ -0,0 +1 @@ +<?php phpinfo(); ?> diff --git a/tests/docroot/Makefile.am b/tests/docroot/Makefile.am new file mode 100644 index 00000000..d14aa288 --- /dev/null +++ b/tests/docroot/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=123 www diff --git a/tests/docroot/www/Makefile.am b/tests/docroot/www/Makefile.am new file mode 100644 index 00000000..1ea8c469 --- /dev/null +++ b/tests/docroot/www/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST=cgi.php cgi.pl dummydir index.html index.txt phpinfo.php \ + phpself.php redirect.php cgi-pathinfo.pl phphost.php \ + nph-status.pl +SUBDIRS=go indexfile diff --git a/tests/docroot/www/cgi-pathinfo.pl b/tests/docroot/www/cgi-pathinfo.pl new file mode 100644 index 00000000..af8d4dfb --- /dev/null +++ b/tests/docroot/www/cgi-pathinfo.pl @@ -0,0 +1,7 @@ +#! /usr/bin/perl + +print "Content-Type: text/html\r\n\r\n"; + +print $ENV{"PATH_INFO"}; + +0; diff --git a/tests/docroot/www/cgi.php b/tests/docroot/www/cgi.php new file mode 100755 index 00000000..d92e52f9 --- /dev/null +++ b/tests/docroot/www/cgi.php @@ -0,0 +1,9 @@ +<?php + +#ob_start(/*"ob_gzhandler"*/); +print "12345<br />\n"; +#phpinfo(); +#header("Content-Length: ".ob_get_length()); +#ob_end_flush(); + +?> diff --git a/tests/docroot/www/cgi.pl b/tests/docroot/www/cgi.pl new file mode 100644 index 00000000..9695adf6 --- /dev/null +++ b/tests/docroot/www/cgi.pl @@ -0,0 +1,7 @@ +#! /usr/bin/perl + +print "Content-Type: text/html\r\n\r\n"; + +print $ENV{"SCRIPT_NAME"}; + +0; diff --git a/tests/docroot/www/go/Makefile.am b/tests/docroot/www/go/Makefile.am new file mode 100644 index 00000000..fe20c503 --- /dev/null +++ b/tests/docroot/www/go/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST=cgi.php diff --git a/tests/docroot/www/go/cgi.php b/tests/docroot/www/go/cgi.php new file mode 100755 index 00000000..d92e52f9 --- /dev/null +++ b/tests/docroot/www/go/cgi.php @@ -0,0 +1,9 @@ +<?php + +#ob_start(/*"ob_gzhandler"*/); +print "12345<br />\n"; +#phpinfo(); +#header("Content-Length: ".ob_get_length()); +#ob_end_flush(); + +?> diff --git a/tests/docroot/www/index.html b/tests/docroot/www/index.html new file mode 100644 index 00000000..3c149671 --- /dev/null +++ b/tests/docroot/www/index.html @@ -0,0 +1,113 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +<TITLE>Webserver testpage</TITLE> +<META content="iso-8859-1" http-equiv="charset"> +<META name="author" content="Christian Hofmann, SuSE Linux AG"> +</HEAD> +<BODY BGCOLOR="#ffffff" LINK="#669900" ALINK="#669900" VLINK="#fb8000" text=black marginwidth="0" marginheight="0" leftmargin="0" topmargin="0"> +<TABLE border="0" width="760" cellpadding="0" cellspacing="0"> + <TR> + <TD rowspan="4" bgcolor="#669900" width="50"> </TD> + <td valign=top height="70" width="29" bgcolor="#669900"> </td> + <td valign="middle" height="70" width="152" align="center" bgcolor="#669900"> </td> + <td valign=middle height="70" width="529" bgcolor="#669900"> + <div align="center"> + <font face="Courier New,Courier,mono" size="5" color="white"><B>+++ testinfo - webserver +++</b></font> + </div> + </td> + </tr> + <TR> + <td width="29" height="100" valign="middle" bgcolor="#669900"> </td> + <td width="152" height="100" align="center" valign="bottom"><IMG src="gif/penguin.gif" width=90 height=76 hspace=0 vspace=0 border=0 alt=" "></td> + <td width="529" height="100" valign="middle"> + <div align="center"> + <font face="Courier New,Courier,mono" size="3"><I>This is only a test page for the webserver!</I></font><br> + <font face="Courier New,Courier,mono" size="-1">SuSE is not responsible for the contents of this domain!</font> + </div> + </td> + </tr> + <tr> + <td valign=top align=left colspan="4"> + <table border="0" cellpadding="0" cellspacing="0" width="710" vspace="0" hspace="0"> + <TR> + <TD bgcolor="#669900" width="29" height="20"> </TD> + <TD width="152" height="20"> </TD> + <TD width="558" height="20" colspan="2"> </TD> + </tr> + <TR> + <TD height="50" align=left width="181" colspan="2"><IMG src="gif/sysinfo_en.png" width="181" height="50" border="0" alt="system information"></TD> + <TD width="529" colspan="2" rowspan="2" valign=top> + <FONT face="helvetica, arial, sans-serif"> + <B>Operating system:</B> [ SuSE Linux 8.0 (i386) + ]<BR> + <B>Host:</B> [ grisu.home.kneschke.de, Kernel: 2.4.18-4GB (i686) + ] + </FONT> + </TD> + </TR> + <TR> + <TD bgcolor="#669900" width="29" height="40"> </TD> + <TD width="181" height="40"> </TD> + </TR> + + <TR> + <TD height="50" align=left width="181" colspan="2"><IMG src="gif/version_en.png" width="181" height="50" border="0" alt="webserver and modules"></TD> + <TD width="529" colspan="2" rowspan="2" valign=top> + <FONT face="helvetica,arial,sans-serif"> + <B>Webserver version:</B><br> + [ lighttpd/0.1.0 (Unix) ]<br><br> + + <B>Installed modules:</B><br> + <I>[ PHP module is not installed ]</I><br> + <I>[ Apache perl module (mod_perl) is not installed ]</I><br> + <I>[ Apache DAV module (mod_dav) is not installed ]</I><br> + <I>[ Apache Python module (mod_python) is not installed ]</I><br> + + </FONT> + </TD> + </TR> + <TR> + <TD bgcolor="#669900" width="29" height="100"> </TD> + <TD width="181" height="100"> </TD> + </TR> + <TR> + <TD colspan="2" height="50" align=left width="181"><IMG src="gif/docu_en.png" width="181" height="50" border="0" alt="documentation"></TD> + <TD width="529" colspan="2" rowspan="2" valign=top> + <FONT face="helvetica,arial,sans-serif"> +<I>[ This host is not configured as server for the SuSE help system ]</I><br><br> +<I>[ Apache manual is not installed ]</I><br> + + <BR> + <A HREF="http://www.suse.de/">[ The SuSE website ]</A><BR> + + + + </FONT> + </TD> + + </TR> + <TR> + <TD bgcolor="#669900" width="29" height="90"> </TD> + <TD width="181" height="90"> </TD> + </Table> + </td> + </tr> + <tr> + <td width=29 bgcolor="#669900"> </td> + <td valign=bottom align=right width="681" colspan="3"><A HREF="http://www.suse.de/en/"><IMG src="gif/powered_by_suse.gif" alt="powered by SuSE" width=100 height=40 hspace=5 vspace=5 border=0></A></td> + </tr> + +</TABLE> +</BODY> +</HTML> + + + + + + + + + + diff --git a/tests/docroot/www/index.txt b/tests/docroot/www/index.txt new file mode 100644 index 00000000..3c149671 --- /dev/null +++ b/tests/docroot/www/index.txt @@ -0,0 +1,113 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> +<TITLE>Webserver testpage</TITLE> +<META content="iso-8859-1" http-equiv="charset"> +<META name="author" content="Christian Hofmann, SuSE Linux AG"> +</HEAD> +<BODY BGCOLOR="#ffffff" LINK="#669900" ALINK="#669900" VLINK="#fb8000" text=black marginwidth="0" marginheight="0" leftmargin="0" topmargin="0"> +<TABLE border="0" width="760" cellpadding="0" cellspacing="0"> + <TR> + <TD rowspan="4" bgcolor="#669900" width="50"> </TD> + <td valign=top height="70" width="29" bgcolor="#669900"> </td> + <td valign="middle" height="70" width="152" align="center" bgcolor="#669900"> </td> + <td valign=middle height="70" width="529" bgcolor="#669900"> + <div align="center"> + <font face="Courier New,Courier,mono" size="5" color="white"><B>+++ testinfo - webserver +++</b></font> + </div> + </td> + </tr> + <TR> + <td width="29" height="100" valign="middle" bgcolor="#669900"> </td> + <td width="152" height="100" align="center" valign="bottom"><IMG src="gif/penguin.gif" width=90 height=76 hspace=0 vspace=0 border=0 alt=" "></td> + <td width="529" height="100" valign="middle"> + <div align="center"> + <font face="Courier New,Courier,mono" size="3"><I>This is only a test page for the webserver!</I></font><br> + <font face="Courier New,Courier,mono" size="-1">SuSE is not responsible for the contents of this domain!</font> + </div> + </td> + </tr> + <tr> + <td valign=top align=left colspan="4"> + <table border="0" cellpadding="0" cellspacing="0" width="710" vspace="0" hspace="0"> + <TR> + <TD bgcolor="#669900" width="29" height="20"> </TD> + <TD width="152" height="20"> </TD> + <TD width="558" height="20" colspan="2"> </TD> + </tr> + <TR> + <TD height="50" align=left width="181" colspan="2"><IMG src="gif/sysinfo_en.png" width="181" height="50" border="0" alt="system information"></TD> + <TD width="529" colspan="2" rowspan="2" valign=top> + <FONT face="helvetica, arial, sans-serif"> + <B>Operating system:</B> [ SuSE Linux 8.0 (i386) + ]<BR> + <B>Host:</B> [ grisu.home.kneschke.de, Kernel: 2.4.18-4GB (i686) + ] + </FONT> + </TD> + </TR> + <TR> + <TD bgcolor="#669900" width="29" height="40"> </TD> + <TD width="181" height="40"> </TD> + </TR> + + <TR> + <TD height="50" align=left width="181" colspan="2"><IMG src="gif/version_en.png" width="181" height="50" border="0" alt="webserver and modules"></TD> + <TD width="529" colspan="2" rowspan="2" valign=top> + <FONT face="helvetica,arial,sans-serif"> + <B>Webserver version:</B><br> + [ lighttpd/0.1.0 (Unix) ]<br><br> + + <B>Installed modules:</B><br> + <I>[ PHP module is not installed ]</I><br> + <I>[ Apache perl module (mod_perl) is not installed ]</I><br> + <I>[ Apache DAV module (mod_dav) is not installed ]</I><br> + <I>[ Apache Python module (mod_python) is not installed ]</I><br> + + </FONT> + </TD> + </TR> + <TR> + <TD bgcolor="#669900" width="29" height="100"> </TD> + <TD width="181" height="100"> </TD> + </TR> + <TR> + <TD colspan="2" height="50" align=left width="181"><IMG src="gif/docu_en.png" width="181" height="50" border="0" alt="documentation"></TD> + <TD width="529" colspan="2" rowspan="2" valign=top> + <FONT face="helvetica,arial,sans-serif"> +<I>[ This host is not configured as server for the SuSE help system ]</I><br><br> +<I>[ Apache manual is not installed ]</I><br> + + <BR> + <A HREF="http://www.suse.de/">[ The SuSE website ]</A><BR> + + + + </FONT> + </TD> + + </TR> + <TR> + <TD bgcolor="#669900" width="29" height="90"> </TD> + <TD width="181" height="90"> </TD> + </Table> + </td> + </tr> + <tr> + <td width=29 bgcolor="#669900"> </td> + <td valign=bottom align=right width="681" colspan="3"><A HREF="http://www.suse.de/en/"><IMG src="gif/powered_by_suse.gif" alt="powered by SuSE" width=100 height=40 hspace=5 vspace=5 border=0></A></td> + </tr> + +</TABLE> +</BODY> +</HTML> + + + + + + + + + + diff --git a/tests/docroot/www/indexfile/Makefile.am b/tests/docroot/www/indexfile/Makefile.am new file mode 100644 index 00000000..2487a42b --- /dev/null +++ b/tests/docroot/www/indexfile/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST=index.php return-404.php diff --git a/tests/docroot/www/indexfile/index.php b/tests/docroot/www/indexfile/index.php new file mode 100644 index 00000000..e0c7d9ec --- /dev/null +++ b/tests/docroot/www/indexfile/index.php @@ -0,0 +1 @@ +<?php print $_SERVER["PHP_SELF"]; ?> diff --git a/tests/docroot/www/indexfile/return-404.php b/tests/docroot/www/indexfile/return-404.php new file mode 100644 index 00000000..dd680cc5 --- /dev/null +++ b/tests/docroot/www/indexfile/return-404.php @@ -0,0 +1,5 @@ +<?php + header("Status: 404"); + + print $_SERVER["PHP_SELF"]; +?> diff --git a/tests/docroot/www/nph-status.pl b/tests/docroot/www/nph-status.pl new file mode 100755 index 00000000..602cdc70 --- /dev/null +++ b/tests/docroot/www/nph-status.pl @@ -0,0 +1,4 @@ +#!/usr/bin/perl + +print "HTTP/1.0 30 FooBar\r\n"; +print "\r\n"; diff --git a/tests/docroot/www/phphost.php b/tests/docroot/www/phphost.php new file mode 100644 index 00000000..edf2f68e --- /dev/null +++ b/tests/docroot/www/phphost.php @@ -0,0 +1,3 @@ +<?php + print $_SERVER["SERVER_NAME"]; +?> diff --git a/tests/docroot/www/phpinfo.php b/tests/docroot/www/phpinfo.php new file mode 100644 index 00000000..147cebcd --- /dev/null +++ b/tests/docroot/www/phpinfo.php @@ -0,0 +1 @@ +<?php phpinfo(); ?> diff --git a/tests/docroot/www/phpself.php b/tests/docroot/www/phpself.php new file mode 100644 index 00000000..b78b9953 --- /dev/null +++ b/tests/docroot/www/phpself.php @@ -0,0 +1,3 @@ +<?php + print $_SERVER["PHP_SELF"]; +?> diff --git a/tests/docroot/www/redirect.php b/tests/docroot/www/redirect.php new file mode 100644 index 00000000..0489d22d --- /dev/null +++ b/tests/docroot/www/redirect.php @@ -0,0 +1,4 @@ +<?php + + header('Location: http://www.example.org:2048/'); +?> diff --git a/tests/fastcgi-01.sh b/tests/fastcgi-01.sh new file mode 100755 index 00000000..d134a160 --- /dev/null +++ b/tests/fastcgi-01.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "FastCGI PHPinfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +FastCGI PHPinfo +GET /phpinfo.php HTTP/1.0 +Host: www.example.org + +Status: 200 +EOF + +run_test + diff --git a/tests/fastcgi-02.sh b/tests/fastcgi-02.sh new file mode 100755 index 00000000..79a0aacb --- /dev/null +++ b/tests/fastcgi-02.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "FastCGI - missing File" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +FastCGI - missing File +GET /phpinfajdhdo.php HTTP/1.1 +Host: www.example.org + +Status: 404 +EOF + +run_test diff --git a/tests/fastcgi-03.sh b/tests/fastcgi-03.sh new file mode 100755 index 00000000..f18c2f87 --- /dev/null +++ b/tests/fastcgi-03.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "index-file -> FastCGI" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +index-file -> FastCGI +GET /go/ HTTP/1.0 +Host: www.example.org + +Status: 200 +EOF + +run_test diff --git a/tests/fastcgi-04.sh b/tests/fastcgi-04.sh new file mode 100755 index 00000000..4b5eb738 --- /dev/null +++ b/tests/fastcgi-04.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +Redirect in PHP +GET /redirect.php HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 302 +Location: http://www.example.org:2048/ +EOF + +run_test diff --git a/tests/fastcgi-05.sh b/tests/fastcgi-05.sh new file mode 100755 index 00000000..e0aecc7e --- /dev/null +++ b/tests/fastcgi-05.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +PHP_SELF +GET /phpself.php HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 200 +Content: /phpself.php +EOF + +run_test diff --git a/tests/fastcgi-06.sh b/tests/fastcgi-06.sh new file mode 100755 index 00000000..bc54d924 --- /dev/null +++ b/tests/fastcgi-06.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +PHP_SELF + PATH_INFO +GET /phpself.php/foo HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 200 +Content: /phpself.php +EOF + +run_test diff --git a/tests/fastcgi-07.sh b/tests/fastcgi-07.sh new file mode 100755 index 00000000..ef148d24 --- /dev/null +++ b/tests/fastcgi-07.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +SERVER_NAME + known host, simplevhost +GET /phphost.php HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 200 +Content: www.example.org +EOF + +run_test diff --git a/tests/fastcgi-08.sh b/tests/fastcgi-08.sh new file mode 100755 index 00000000..b96b1120 --- /dev/null +++ b/tests/fastcgi-08.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +SERVER_NAME + unknown host, default +GET /phphost.php HTTP/1.0 +Host: xxx.example.org +Conntection: close + +Status: 200 +Content: www.example.org +EOF + +run_test diff --git a/tests/fastcgi-09.sh b/tests/fastcgi-09.sh new file mode 100755 index 00000000..401e2c07 --- /dev/null +++ b/tests/fastcgi-09.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +SERVER_NAME + \$HTTP["Host"], default +GET /phphost.php HTTP/1.0 +Host: vvv.example.org +Conntection: close + +Status: 200 +Content: www.example.org +EOF + +run_test diff --git a/tests/fastcgi-10.conf b/tests/fastcgi-10.conf new file mode 100644 index 00000000..f2b39db3 --- /dev/null +++ b/tests/fastcgi-10.conf @@ -0,0 +1,156 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", +# "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_accesslog" ) + +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +fastcgi.debug = 0 +fastcgi.server = ( ".php" => ( + "grisu" => ( + "host" => "192.168.0.2", + "port" => 1026 + ) +# "ulf" => ( +# "host" => "192.168.2.41", +# "docroot" => "/home/jan/servers/", +# "port" => 1026 +# ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + + + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/fastcgi-10.sh b/tests/fastcgi-10.sh new file mode 100755 index 00000000..bd0e8dd0 --- /dev/null +++ b/tests/fastcgi-10.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "Redirect in PHP" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +SERVER_NAME + \$HTTP, servername +GET /phphost.php HTTP/1.0 +Host: zzz.example.org +Conntection: close + +Status: 200 +Content: zzz.example.org +EOF + +run_test diff --git a/tests/fastcgi-11.conf b/tests/fastcgi-11.conf new file mode 100644 index 00000000..2b7f73ff --- /dev/null +++ b/tests/fastcgi-11.conf @@ -0,0 +1,164 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +debug.log-request-header = "enable" +debug.log-response-header = "enable" +debug.log-request-handling = "enable" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", +# "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_accesslog" ) + +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +fastcgi.debug = 0 +fastcgi.server = ( "/" => ( + "grisu" => ( + "host" => "192.168.0.2", + "port" => 1027, + "bin-path" => "./fcgi-auth", + "mode" => "authorizer", + "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/", + + ) +# "ulf" => ( +# "host" => "192.168.2.41", +# "docroot" => "/home/jan/servers/", +# "port" => 1026 +# ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + + + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/fastcgi-11.sh b/tests/fastcgi-11.sh new file mode 100755 index 00000000..2042c1d3 --- /dev/null +++ b/tests/fastcgi-11.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +FastCGI-Auth - ok +GET /index.html?ok HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 200 +EOF + +run_test diff --git a/tests/fastcgi-12.conf b/tests/fastcgi-12.conf new file mode 100644 index 00000000..2b7f73ff --- /dev/null +++ b/tests/fastcgi-12.conf @@ -0,0 +1,164 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +debug.log-request-header = "enable" +debug.log-response-header = "enable" +debug.log-request-handling = "enable" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", +# "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_accesslog" ) + +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +fastcgi.debug = 0 +fastcgi.server = ( "/" => ( + "grisu" => ( + "host" => "192.168.0.2", + "port" => 1027, + "bin-path" => "./fcgi-auth", + "mode" => "authorizer", + "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/", + + ) +# "ulf" => ( +# "host" => "192.168.2.41", +# "docroot" => "/home/jan/servers/", +# "port" => 1026 +# ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + + + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/fastcgi-12.sh b/tests/fastcgi-12.sh new file mode 100755 index 00000000..0eed8cd3 --- /dev/null +++ b/tests/fastcgi-12.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +FastCGI-Authorizer - 403 +GET /index.html?fail HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 403 +EOF + +run_test diff --git a/tests/fastcgi-13.conf b/tests/fastcgi-13.conf new file mode 100644 index 00000000..cb12269a --- /dev/null +++ b/tests/fastcgi-13.conf @@ -0,0 +1,156 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +debug.log-request-header = "enable" +debug.log-response-header = "enable" +debug.log-request-handling = "enable" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", +# "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_accesslog" ) + +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +fastcgi.debug = 0 +fastcgi.server = ( ".php" => ( + "grisu" => ( + "host" => "127.0.0.1", + "port" => 1048, + "bin-path" => "/home/weigon/Documents/php-4.3.10/sapi/cgi/php -c /usr/local/lib/php.ini", + ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + + + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/fastcgi-13.sh b/tests/fastcgi-13.sh new file mode 100755 index 00000000..75cbac92 --- /dev/null +++ b/tests/fastcgi-13.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +if test -e /home/weigon/Documents/php-4.3.10/sapi/cgi/php > /dev/null; then + echo -n +else + printf "%-40s" "FastCGI PHPinfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + + +cat > $TMPFILE <<EOF +FastCGI + local-spawning +GET /indexfile/index.php HTTP/1.0 +Host: www.example.org +Conntection: close + +Status: 200 +EOF + +run_test diff --git a/tests/fcgi-auth.c b/tests/fcgi-auth.c new file mode 100644 index 00000000..ddecddc8 --- /dev/null +++ b/tests/fcgi-auth.c @@ -0,0 +1,26 @@ +#include <fcgi_stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +int main () { + char* p; + + while (FCGI_Accept() >= 0) { + /* wait for fastcgi authorizer request */ + + printf("Content-type: text/html\r\n"); + + if (((p = getenv("QUERY_STRING")) == NULL) || + strcmp(p, "ok") != 0) { + printf("Status: 403 Forbidden\r\n\r\n"); + } else { + printf("\r\n"); + /* default Status is 200 - allow access */ + } + + printf("foobar\r\n"); + } + + return 0; +} diff --git a/tests/head-01.sh b/tests/head-01.sh new file mode 100755 index 00000000..510fb350 --- /dev/null +++ b/tests/head-01.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +HEAD request should have no content +HEAD /index.html HTTP/1.0 + +Status: 200 +EOF + +run_test_script + +if test x$exitcode = x0; then + if test `cat $NAME.out | sed '1,/^.$/d' | wc -l` = 0; then + a=a + else + exitcode=-1 + fi +fi + +run_test_exit diff --git a/tests/host-01.sh b/tests/host-01.sh new file mode 100755 index 00000000..95b36176 --- /dev/null +++ b/tests/host-01.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host set to weigon.dyndns.org +GET / HTTP/1.0 +Host: weigon.dyndns.org + +Status: 200 +EOF + +run_test diff --git a/tests/host-02.sh b/tests/host-02.sh new file mode 100755 index 00000000..22ed7f7c --- /dev/null +++ b/tests/host-02.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host not set +GET / HTTP/1.0 + +Status: 200 +EOF + +run_test diff --git a/tests/host-03.sh b/tests/host-03.sh new file mode 100755 index 00000000..6ac03c32 --- /dev/null +++ b/tests/host-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host not set +GET / HTTP/1.0 +Host: grisu.home.kneschke.de + +Status: 200 +EOF + +run_test diff --git a/tests/host-04.sh b/tests/host-04.sh new file mode 100755 index 00000000..b8936c34 --- /dev/null +++ b/tests/host-04.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host not set +GET / HTTP/1.0 +Host: ../123.org/ + +Status: 400 +EOF + +run_test diff --git a/tests/host-05.sh b/tests/host-05.sh new file mode 100755 index 00000000..d0999b8d --- /dev/null +++ b/tests/host-05.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +IPv6 +GET / HTTP/1.0 +Host: [::1]:80 + +Status: 200 +EOF + +run_test diff --git a/tests/http11-01.sh b/tests/http11-01.sh new file mode 100755 index 00000000..592c6ba1 --- /dev/null +++ b/tests/http11-01.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +valid HTTP/1.1 request +GET / HTTP/1.1 +Host: weigon.dyndns.org + +Status: 200 +EOF + +run_test diff --git a/tests/http11-02.sh b/tests/http11-02.sh new file mode 100755 index 00000000..025fd2fe --- /dev/null +++ b/tests/http11-02.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +valid HTTP/1.1 request + req: Date +GET / HTTP/1.1 +Host: weigon.dyndns.org + +Status: 200 +MUST: Date +EOF + +run_test diff --git a/tests/http11-03.sh b/tests/http11-03.sh new file mode 100755 index 00000000..4162566e --- /dev/null +++ b/tests/http11-03.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +HTTP/1.0 + absoluteURI +GET http://www.example.org/ HTTP/1.0 + +Status: 200 +EOF + +run_test diff --git a/tests/large-header-01.sh b/tests/large-header-01.sh new file mode 100755 index 00000000..bd7744b5 --- /dev/null +++ b/tests/large-header-01.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +large request header +GET / HTTP/1.0 +Hsgfsdjf: asdfhdf +hdhd: shdfhfdasd +hfhr: jfghsdfg +jfuuehdmn: sfdgjfdg +jvcbzufdg: sgfdfg +hrnvcnd: jfjdfg +jfusfdngmd: gfjgfdusdfg +nfj: jgfdjdfg +jfue: jfdfdg + +Status: 200 +EOF + +run_test diff --git a/tests/leak-01.sh b/tests/leak-01.sh new file mode 100755 index 00000000..a7cad3d7 --- /dev/null +++ b/tests/leak-01.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: leading dot +GET / HTTP/1.0 +Host: .jsdh.sfdg.sdfg. + +Status: 400 +EOF + +run_test diff --git a/tests/leak-02.sh b/tests/leak-02.sh new file mode 100755 index 00000000..08142ca7 --- /dev/null +++ b/tests/leak-02.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: good name +GET / HTTP/1.0 +Host: jsdh.sfdg.sdfg + +Status: 200 +EOF + +run_test diff --git a/tests/leak-03.sh b/tests/leak-03.sh new file mode 100755 index 00000000..a54833f5 --- /dev/null +++ b/tests/leak-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: two dots +GET / HTTP/1.0 +Host: .jsdh..sfdg.sdfg + +Status: 400 +EOF + +run_test diff --git a/tests/leak-04.sh b/tests/leak-04.sh new file mode 100755 index 00000000..169ecb42 --- /dev/null +++ b/tests/leak-04.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: trailing dot +GET / HTTP/1.0 +Host: .jsdh.sfdg.sdfg.:aasd + +Status: 400 +EOF + +run_test diff --git a/tests/leak-05.sh b/tests/leak-05.sh new file mode 100755 index 00000000..22dd7091 --- /dev/null +++ b/tests/leak-05.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: missing host +GET / HTTP/1.0 +Host: :.jsdh.sfdg.sdfg. + +Status: 400 +EOF + +run_test diff --git a/tests/leak-06.sh b/tests/leak-06.sh new file mode 100755 index 00000000..5d9542c7 --- /dev/null +++ b/tests/leak-06.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: broken host/port +GET / HTTP/1.0 +Host: .jsdh.sfdg.:sdfg. + +Status: 400 +EOF + +run_test diff --git a/tests/leak-07.sh b/tests/leak-07.sh new file mode 100755 index 00000000..a4f97eea --- /dev/null +++ b/tests/leak-07.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: broken name/port +GET / HTTP/1.0 +Host: ..jsdh..sfdg..:sdfg. + +Status: 400 +EOF + +run_test diff --git a/tests/leak-08.sh b/tests/leak-08.sh new file mode 100755 index 00000000..aa6d4637 --- /dev/null +++ b/tests/leak-08.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: good name +GET / HTTP/1.0 +Host: abc.de + +Status: 200 +EOF + +run_test diff --git a/tests/leak-09.sh b/tests/leak-09.sh new file mode 100755 index 00000000..5afd85fd --- /dev/null +++ b/tests/leak-09.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: good name +GET / HTTP/1.0 +Host: a-b.de + +Status: 200 +EOF + +run_test diff --git a/tests/leak-10.sh b/tests/leak-10.sh new file mode 100755 index 00000000..694e99b9 --- /dev/null +++ b/tests/leak-10.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: leading dash +GET / HTTP/1.0 +Host: -ab.de + +Status: 400 +EOF + +run_test diff --git a/tests/leak-11.sh b/tests/leak-11.sh new file mode 100755 index 00000000..feef7ca3 --- /dev/null +++ b/tests/leak-11.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: good name +GET / HTTP/1.0 +Host: a-b.de124 + +Status: 200 +EOF + +run_test diff --git a/tests/leak-12.sh b/tests/leak-12.sh new file mode 100755 index 00000000..df5012d9 --- /dev/null +++ b/tests/leak-12.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: broken name +GET / HTTP/1.0 +Host: . + +Status: 400 +EOF + +run_test diff --git a/tests/leak-13.sh b/tests/leak-13.sh new file mode 100755 index 00000000..65aa1560 --- /dev/null +++ b/tests/leak-13.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: broken port +GET / HTTP/1.0 +Host: abc.de:avs + +Status: 400 +EOF + +run_test diff --git a/tests/leak-14.sh b/tests/leak-14.sh new file mode 100755 index 00000000..7451e4d6 --- /dev/null +++ b/tests/leak-14.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: good name +GET / HTTP/1.0 +Host: abc.de:1234 + +Status: 200 +EOF + +run_test diff --git a/tests/leak-15.sh b/tests/leak-15.sh new file mode 100755 index 00000000..f2691639 --- /dev/null +++ b/tests/leak-15.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: good IP +GET / HTTP/1.0 +Host: 192.168.2.10:1234 + +Status: 200 +EOF + +run_test diff --git a/tests/leak-16.sh b/tests/leak-16.sh new file mode 100755 index 00000000..f6d589c8 --- /dev/null +++ b/tests/leak-16.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: broken name/ip +GET / HTTP/1.0 +Host: a192.168.2.10:1234 + +Status: 400 +EOF + +run_test diff --git a/tests/leak-17.sh b/tests/leak-17.sh new file mode 100755 index 00000000..7884fb42 --- /dev/null +++ b/tests/leak-17.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Host: broken IP +GET / HTTP/1.0 +Host: 192.168.2:1234 + +Status: 400 +EOF + +run_test diff --git a/tests/lighttpd.conf b/tests/lighttpd.conf new file mode 100644 index 00000000..4a91e72e --- /dev/null +++ b/tests/lighttpd.conf @@ -0,0 +1,166 @@ +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +## bind to port (default: 80) +server.port = 2048 + +# server.license = "00000001000000013feccb804014587f000000010000000105911c976a3d462c8eaa2d7ca850432c" + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + +fastcgi.debug = 1 + +## +## Format: <errorfile-prefix><status>.html +## -> ..../status-404.html for 'File not found' +#server.errorfile-prefix = "/home/weigon/projects/lighttpd/doc/status-" + +server.dir-listing = "enable" + +#server.event-handler = "linux-sysepoll" +#server.event-handler = "linux-rtsig" + +#server.modules.path = "" +server.modules = ( + "mod_rewrite", + "mod_setenv", + "mod_access", + "mod_auth", +# "mod_httptls", + "mod_status", + "mod_expire", + "mod_simple_vhost", + "mod_redirect", +# "mod_evhost", +# "mod_localizer", + "mod_fastcgi", + "mod_cgi", + "mod_compress", + "mod_userdir", + "mod_accesslog" ) + +server.indexfiles = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +#,-- only root can use these options +#| +#|# chroot() to directory (default: no chroot() ) +#| server.chroot / +#|# change uid to <uid> (default: don't care) +#| server.userid wwwrun +#|# change uid to <uid> (default: don't care) +#| server.groupid wwwrun +#| +#`-- + + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".png" => "image/png", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".html" => "text/html", + ".htm" => "text/html", + ".pdf" => "application/pdf", + ".swf" => "application/x-shockwave-flash", + ".spl" => "application/futuresplash", + ".txt" => "text/plain", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".gz" => "application/x-gzip", + ".c" => "text/plain", + ".conf" => "text/plain" ) + +compress.cache-dir = "/tmp/lighttpd/cache/compress/" +compress.filetype = ("text/plain", "text/html") + +setenv.add-environment = ( "TRAC_ENV" => "foo") +setenv.add-request-header = ( "FOO" => "foo") +setenv.add-response-header = ( "BAR" => "foo") + +fastcgi.debug = 0 +fastcgi.server = ( ".php" => ( + "grisu" => ( + "host" => "192.168.0.2", + "port" => 1026, +# "mode" => "authorizer", +# "docroot" => "/tmp/lighttpd/servers/www.example.org/pages/", + ) + ) + ) + + +cgi.assign = ( ".pl" => "/usr/bin/perl", + ".cgi" => "/usr/bin/perl", + ".py" => "/usr/bin/python" ) + +userdir.include-user = ( "weigon" ) +userdir.path = "/" + +ssl.engine = "disable" +ssl.pemfile = "server.pem" + +auth.backend = "plain" +auth.backend.plain.userfile = "/tmp/lighttpd/lighttpd.user" +auth.backend.plain.groupfile = "lighttpd.group" + +auth.backend.ldap.hostname = "localhost" +auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +auth.backend.ldap.filter = "(uid=$)" + +auth.require = ( "/server-status" => + ( + "method" => "digest", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ), + "/auth.php" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "host=192.168.2.10") + "require" => "user=jan" + ), + "/server-config" => + ( + "method" => "basic", + "realm" => "download archiv", +# "require" => ("group=www", "user=jan", "user=weigon", "host=192.168.2.10") + "require" => "group=www|user=jan|host=192.168.2.10" + ) + ) + +url.access-deny = ( "~", ".inc") + +url.redirect = ( "^/redirect/$" => "http://localhost:2048/" ) + +expire.url = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes") + +#cache.cache-dir = "/home/weigon/wwwroot/cache/" + +#### status module +status.status-url = "/server-status" +status.config-url = "/server-config" + +simple-vhost.document-root = "pages" +simple-vhost.server-root = "/tmp/lighttpd/servers/" +simple-vhost.default-host = "www.example.org" + +$HTTP["host"] == "vvv.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +} + +$HTTP["host"] == "zzz.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "zzz.example.org" +} + diff --git a/tests/lighttpd.user b/tests/lighttpd.user new file mode 100644 index 00000000..020aedc6 --- /dev/null +++ b/tests/lighttpd.user @@ -0,0 +1 @@ +jan:jan diff --git a/tests/missing-01.sh b/tests/missing-01.sh new file mode 100755 index 00000000..60518800 --- /dev/null +++ b/tests/missing-01.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +missing file +GET /cjhdhfdjgfdg HTTP/1.0 + +Status: 404 +EOF + +run_test diff --git a/tests/missing-02.sh b/tests/missing-02.sh new file mode 100755 index 00000000..b3c61162 --- /dev/null +++ b/tests/missing-02.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +missing file + get-vars +GET /cjhdhfdjgfdg?jdfjh=dnfdh HTTP/1.0 + +Status: 404 +EOF + +run_test diff --git a/tests/pathinfo-01.sh b/tests/pathinfo-01.sh new file mode 100755 index 00000000..17b7a50a --- /dev/null +++ b/tests/pathinfo-01.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "PathInfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +PathInfo +GET /cgi.php/abc HTTP/1.0 + +Status: 200 +EOF + +run_test + diff --git a/tests/pathinfo-02.sh b/tests/pathinfo-02.sh new file mode 100755 index 00000000..08d7dde6 --- /dev/null +++ b/tests/pathinfo-02.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if pidof php > /dev/null; then + echo -n +else + printf "%-40s" "PathInfo" + exit 77 +fi + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +PathInfo on a directory +GET /www/abc/def HTTP/1.0 + +Status: 404 +EOF + +run_test + diff --git a/tests/post-01.sh b/tests/post-01.sh new file mode 100755 index 00000000..5f060091 --- /dev/null +++ b/tests/post-01.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +POST without Content-Length +POST / HTTP/1.0 + +Status: 411 +EOF + +run_test diff --git a/tests/post-02.sh b/tests/post-02.sh new file mode 100755 index 00000000..3b2889a2 --- /dev/null +++ b/tests/post-02.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +POST without Content-Length +POST / HTTP/1.0 +Content-type: application/x-www-form-urlencoded +Content-length: 0 + +Status: 200 +EOF + +run_test diff --git a/tests/prepare.sh b/tests/prepare.sh new file mode 100755 index 00000000..faa7e220 --- /dev/null +++ b/tests/prepare.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +tmpdir=/tmp/lighttpd + +if test x$srcdir = x; then + srcdir=. +fi + +# create test-framework +rm -rf $tmpdir +mkdir -p $tmpdir/servers/www.example.org/pages/ +mkdir -p $tmpdir/servers/www.example.org/pages/dummydir/ +mkdir -p $tmpdir/servers/www.example.org/pages/go/ +mkdir -p $tmpdir/servers/www.example.org/pages/indexfile/ +mkdir -p $tmpdir/servers/123.example.org/pages/ +mkdir -p $tmpdir/logs/ +mkdir -p $tmpdir/cache/ +mkdir -p $tmpdir/cache/compress/ + +# copy everything into the right places +cp $srcdir/docroot/www/*.html \ + $srcdir/docroot/www/*.php \ + $srcdir/docroot/www/*.pl \ + $srcdir/docroot/www/*.txt $tmpdir/servers/www.example.org/pages/ +cp $srcdir/docroot/www/go/*.php $tmpdir/servers/www.example.org/pages/go/ +cp $srcdir/docroot/www/indexfile/*.php $tmpdir/servers/www.example.org/pages/indexfile/ +cp $srcdir/docroot/123/*.txt \ + $srcdir/docroot/123/*.html \ + $srcdir/docroot/123/*.php \ + $srcdir/docroot/123/*.bla $tmpdir/servers/123.example.org/pages/ +cp $srcdir/lighttpd.user $tmpdir/ + +printf "%-40s" "preparing infrastructure" + +exit 0 diff --git a/tests/redirect-01.sh b/tests/redirect-01.sh new file mode 100755 index 00000000..05f4c835 --- /dev/null +++ b/tests/redirect-01.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +Internal Redirect +GET /dummydir HTTP/1.0 + +Status: 301 +Location: http://localhost:2048/dummydir/ +EOF + +run_test diff --git a/tests/redirect-02.sh b/tests/redirect-02.sh new file mode 100755 index 00000000..67b5c31b --- /dev/null +++ b/tests/redirect-02.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +redirect module test +GET /redirect/ HTTP/1.0 + +Status: 301 +Location: http://localhost:2048/ +EOF + +run_test diff --git a/tests/redirect-03.sh b/tests/redirect-03.sh new file mode 100755 index 00000000..96b0b2ea --- /dev/null +++ b/tests/redirect-03.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test x$srcdir = x && srcdir=. + +. $srcdir/testbase.sh + +prepare_test + +cat > $TMPFILE <<EOF +redirect module test +GET /dummydir?foo HTTP/1.0 + +Status: 301 +Location: http://localhost:2048/dummydir/?foo +EOF + +run_test diff --git a/tests/testbase.sh b/tests/testbase.sh new file mode 100644 index 00000000..b4fcfca7 --- /dev/null +++ b/tests/testbase.sh @@ -0,0 +1,63 @@ +exitcode=0 +lighttpdpid=0 +prepare_test () { + test -x $srcdir/conformance.pl || exit 77 + + NAME=`basename $0 | sed s/\.sh$//` + if which mktemp > /dev/null; then + TMPFILE=`mktemp /tmp/$NAME.XXXXXX` || exit 1; + else + TMPFILE=/tmp/$NAME.XXXXXX + fi + + if test x$top_builddir != x; then + # not in stand-alone mode + if test -f /tmp/lighttpd/lighttpd.pid; then + kill `cat /tmp/lighttpd/lighttpd.pid` + rm -f /tmp/lighttpd/lighttpd.pid + fi + + # start webserver + CONF=`echo $0 | sed s/\.sh$/.conf/` + #VALGRIND='valgrind --tool=memcheck --logfile=lighttpd' + VALGRIND= + if test -e $CONF; then + $VALGRIND $top_builddir/src/lighttpd -f $CONF + else + $VALGRIND $top_builddir/src/lighttpd -f $srcdir/lighttpd.conf + fi + test x$? = x0 || exit 1 + + # ps ax > $NAME.psax + fi +} + +run_test_script () { + if test x$top_builddir = x; then + cat $TMPFILE | $srcdir/conformance.pl standalone > $NAME.out + else + cat $TMPFILE | $srcdir/conformance.pl > $NAME.out + fi + + exitcode=$? +} + +run_test_exit () { + if test x$top_builddir != x; then + # stop webserver + kill `cat /tmp/lighttpd/lighttpd.pid` || exit 1 + rm -f /tmp/lighttpd/lighttpd.pid + fi + + if test x$exitcode = x0; then + rm $NAME.out; + fi; + rm -f $TMPFILE + + exit $exitcode; +} + +run_test () { + run_test_script + run_test_exit +} diff --git a/tests/wrapper.sh b/tests/wrapper.sh new file mode 100755 index 00000000..5b8e6064 --- /dev/null +++ b/tests/wrapper.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +## get some parameters from the makefile + +export srcdir=$1 +export top_builddir=$2 + +$3 |