etc: Add environment option to run init.d/ashd silently. master
authorFredrik Tolf <fredrik@dolda2000.com>
Thu, 23 Nov 2023 18:53:06 +0000 (19:53 +0100)
committerFredrik Tolf <fredrik@dolda2000.com>
Thu, 23 Nov 2023 18:53:06 +0000 (19:53 +0100)
32 files changed:
ChangeLog
configure.ac [moved from configure.in with 78% similarity]
doc/Makefile.am
doc/accesslog.doc
doc/dirplex.doc
doc/htparser.doc
doc/htpipe.doc [new file with mode: 0644]
doc/httrcall.doc
doc/patplex.doc
doc/userplex.doc
etc/ashd/dirplex.rc
etc/debian/init.d-ashd
examples/vhosts/run
python/ashd/serve.py
python3/ashd-wsgi3
python3/ashd/async.py
python3/ashd/asyncio.py [new file with mode: 0644]
python3/ashd/serve.py
python3/ashd/util.py
src/.gitignore
src/Makefile.am
src/accesslog.c
src/dirplex/conf.c
src/dirplex/dirplex.c
src/dirplex/dirplex.h
src/htparser.c
src/htparser.h
src/htpipe.c [new file with mode: 0644]
src/patplex.c
src/ratequeue.c [new file with mode: 0644]
src/ssl-gnutls.c
src/ssl-openssl.c [new file with mode: 0644]

index d8c848a..c075a84 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,11 +1,11 @@
 Version 0.13:
 
-       * FreeBSD support.
+       * Partial FreeBSD support.
        * Support upgrading connections to full-duplex, for eg. websockets.
        * More useful dirplex/patplex configuration options.
        * Improved behavior under overload conditions.
        * Expanded accesslog's logging capabilities.
-       * Added httrcall.
+       * Added httrcall and htpipe.
        * Quite a slew of random bugfixes and implementation improvements.
 
 Version 0.12:
similarity index 78%
rename from configure.in
rename to configure.ac
index 80d2389..5f2d06b 100644 (file)
@@ -1,7 +1,7 @@
 AC_INIT([ashd], [0.13])
 AC_CONFIG_SRCDIR(src/htparser.c)
 AM_INIT_AUTOMAKE
-AM_CONFIG_HEADER(config.h)
+AC_CONFIG_HEADERS(config.h)
 AC_USE_SYSTEM_EXTENSIONS
 
 AC_PROG_CC
@@ -9,11 +9,6 @@ AM_PROG_CC_C_O
 AC_PROG_INSTALL
 AC_PROG_RANLIB
 
-dnl Add for libtool:
-dnl AM_PROG_LIBTOOL
-
-AC_HEADER_STDC
-
 HAS_MAGIC=yes
 AC_CHECK_LIB(magic, magic_open, [:], [HAS_MAGIC=no])
 AC_CHECK_HEADER(magic.h, [], [HAS_MAGIC=no])
@@ -102,8 +97,12 @@ fi
 AC_SUBST(XATTR_LIBS)
 
 AH_TEMPLATE(HAVE_GNUTLS, [define to use the GnuTLS library for SSL support])
+AH_TEMPLATE(HAVE_OPENSSL, [define to use the OpenSSL library for SSL support])
 AC_ARG_WITH(gnutls, AS_HELP_STRING([--with-gnutls], [enable SSL support with the GnuTLS library]))
+AC_ARG_WITH(openssl, AS_HELP_STRING([--with-openssl], [enable SSL support with the OpenSSL library]))
 HAS_GNUTLS=""
+HAS_OPENSSL=""
+
 if test "$with_gnutls" = no; then HAS_GNUTLS=no; fi
 if test -z "$HAS_GNUTLS"; then
        AC_CHECK_LIB(gnutls, gnutls_global_init, [:], [HAS_GNUTLS=no])
@@ -123,10 +122,41 @@ fi
 AC_SUBST(GNUTLS_CPPFLAGS)
 AC_SUBST(GNUTLS_LIBS)
 
-AC_OUTPUT([
+if test "$with_openssl" = no; then
+       HAS_OPENSSL=no
+elif test -z "$with_openssl" -a "$HAS_GNUTLS" = yes; then
+       HAS_OPENSSL=no
+fi
+if test -z "$HAS_OPENSSL"; then
+       AC_CHECK_LIB(ssl, SSL_CTX_new, [:], [HAS_OPENSSL=no])
+fi
+if test -z "$HAS_OPENSSL"; then
+       AC_CHECK_LIB(crypto, ERR_get_error, [:], [HAS_OPENSSL=no])
+fi
+if test -z "$HAS_OPENSSL";  then
+       AC_CHECK_HEADER(openssl/ssl.h, [], [HAS_OPENSSL=no])
+fi
+if test "$HAS_OPENSSL" != no; then HAS_OPENSSL=yes; fi
+if test "$with_openssl" = yes -a "$HAS_OPENSSL" = no; then
+       AC_MSG_ERROR([*** cannot find OpenSSL on this system])
+fi
+if test "$HAS_OPENSSL" = yes; then
+       OPENSSL_LIBS="-lssl -lcrypto"
+       GNUTLS_CPPFLAGS=
+       AC_DEFINE(HAVE_OPENSSL)
+fi
+AC_SUBST(OPENSSL_CPPFLAGS)
+AC_SUBST(OPENSSL_LIBS)
+
+if test "$HAS_GNUTLS" = yes -a "$HAS_OPENSSL" = yes; then
+       AC_MSG_ERROR([*** only one of GnuTLS and OpenSSL must be enabled])
+fi
+
+AC_CONFIG_FILES([
 Makefile
 src/Makefile
 src/dirplex/Makefile
 lib/Makefile
 doc/Makefile
 ])
+AC_OUTPUT
index baf94f3..9429bfb 100644 (file)
@@ -1,7 +1,7 @@
 dist_man1_MANS =       callcgi.1 dirplex.1 htparser.1 patplex.1 sendfile.1 \
                        userplex.1 htls.1 callscgi.1 accesslog.1 htextauth.1 \
                        callfcgi.1 multifscgi.1 errlogger.1 httimed.1 \
-                       psendfile.1 httrcall.1
+                       psendfile.1 httrcall.1 htpipe.1
 
 dist_man7_MANS = ashd.7
 
index 2baf508..7a8e8e9 100644 (file)
@@ -199,6 +199,17 @@ instead expand into a dash.
        Expands into the time it took for the handler to complete the
        response, expressed as seconds with 6 decimals precision.
 
+*%{*'HEADER'*}p*::
+
+       Expands into the HTTP response header named by 'HEADER'. If
+       the specified header does not exist in the request, *%p*
+       expands into a dash.
+
+*%{*'HEADER'*}P*::
+
+       Similar to *%p*, except that 'HEADER' is prepended with
+       `X-Ash-`, for simple convenience.
+
 In any expanded field, any "unsafe" characters are escaped. Currently,
 this means that double-quotes and backslashes are prepended with a
 backslash, newlines and tabs are expressed as, respectively, `\n` and
index 8a0184f..c18ca14 100644 (file)
@@ -148,6 +148,19 @@ The following configuration directives are recognized:
        may be given to turn off index file searching completely. The
        *index-file* directive accepts no follow-up lines.
 
+*dot-allow* ['PATTERN'...]::
+
+       As described under 404 RESPONSES, a path element beginning
+       with a dot character is normally rejected by default, but the
+       *dot-allow* directive allows certain dot-files or -directories
+       to be selectively allowed. Each 'PATTERN' is an ordinary glob
+       pattern, the matching of which allows access to a given path
+       element. When checking for access to dot-files or
+       -directories, only the *dot-allow* directive "closest" to the
+       file under consideration is used. It should be noted that the
+       default configuration file for *dirplex* contains a
+       *dot-allow* directive for the `.well-known` directory.
+
 *child* 'NAME'::
 
        Declares a named, persistent request handler (see *ashd*(7)
index 928e642..f400a62 100644 (file)
@@ -81,6 +81,9 @@ OPTIONS
        After having daemonized, write the PID of the new process to
        'PIDFILE'.
 
+If the *-u*, *-r* or *-p* option is presented with an empty argument,
+it will be treated as if the option had not been given.
+
 SIGNALS
 -------
 
@@ -92,6 +95,31 @@ SIGTERM, SIGINT::
        connections open for keep-alive. Upon second reception,
        `htparser` shuts down completely.
 
+PID-FILE PROTOCOL
+-----------------
+
+If the *-p* option is used to create a PID file, `htparser` will
+follow a simple protocol to allow state monitoring for clean shutdown
+purposes. When `htparser` is signalled to terminate, as described
+under SIGNALS, then it appends a single newline at the end of the PID
+file. Once all outstanding connections have been terminated, then
+`htparser` will truncate the PID file to zero size just prior to
+exiting. Thus, init scripts or other state-monitoring tools can know
+that `htparser` is serving remaining connections as long as the PID
+file contains two lines (the last of which is empty).
+
+Further, when `htparser` starts, it does not overwrite the contents of
+an existing PID file, but rather creates a new file, replacing the old
+file. Thus, if a new instance of `htparser` is started while a
+previous instance is still running (or serving remaining connections),
+the PID file for the new instance will not be truncated when the
+previous instance terminates.
+
+The reason for the somewhat unorthodox protocol is that it works by
+simply keeping the PID file open in the running process, allowing the
+protocol to work without pathnames, and therefore even if `htparser`
+is instructed to change root directory with the *-r* option.
+
 EXAMPLES
 --------
 
diff --git a/doc/htpipe.doc b/doc/htpipe.doc
new file mode 100644 (file)
index 0000000..cf90e5f
--- /dev/null
@@ -0,0 +1,71 @@
+htpipe(1)
+==========
+
+NAME
+----
+htpipe - Ashd decoupler pipe
+
+SYNOPSIS
+--------
+*htpipe* [*-h*] [*-CS*] 'SOCKET-PATH' ['CHILD' ['ARGS'...]]
+
+DESCRIPTION
+-----------
+
+The *htpipe* handler implements piping of *ashd*(7) requests over a
+named Unix socket. The main reason for doing so would be to isolate an
+*ashd*(7) handler program from the effects of restarting its parent
+process. In particular, it is useful for isolating the entire handler
+process tree from restarting *htparser*(1), which may be done
+particularly often for such reasons as certificate renewal.
+
+If the *-S* flag is given to start *htpipe* in server mode, *htpipe*
+will start listening to a socket named by 'SOCKET-PATH', start a
+handler program as specified by 'CHILD' and 'ARGS', accept connections
+on said socket, and pass requests received on such connections to the
+handler program. It can handle an arbitrary amount of such
+connections, but it is not implemented for high performance with a
+large number of connections. It is expected that the ordinary case
+will involve very few connections, usually only one at a time.
+
+If the *-C* flag is given to start *htpipe* in client mode, *htpipe*
+will connect to the socket named by 'SOCKET-PATH', accept requests
+from standard input, and pass such requests over said socket,
+presumably to an *htpipe* instance running in server mode on the other
+end.
+
+By default, when neither the *-S* nor the *-C* option is given,
+*htpipe* will attempt to connect to the socket named by 'SOCKET-PATH'
+and, if that succeeds, go on running in client mode, just as if the
+*-C* option had been given. If, however, the connection fails, it will
+fork a copy of itself to run in server mode, and then reconnect to the
+socket presumably created by that new process.
+
+*htpipe* is a persistent handler, as defined in *ashd*(7), and the
+specified child handler must also be a persistent handler.
+
+OPTIONS
+-------
+
+*-h*::
+
+       Print a brief help message to standard output and exit.
+
+*-C*::
+
+       Run in dedicated client mode, as described above. In this
+       mode, the 'CHILD' argument does not need to be given.
+
+*-S*::
+
+       Run in dedicated server mode, as described above. In this
+       mode, as well as when neither the *-C* nor the *-S* option
+       were given, the 'CHILD' argument is mandatory.
+
+AUTHOR
+------
+Fredrik Tolf <fredrik@dolda2000.com>
+
+SEE ALSO
+--------
+*ashd*(7)
index 555284e..514ae6f 100644 (file)
@@ -32,11 +32,11 @@ OPTIONS
 *-l* 'LIMIT'::
 
        If specified, only 'LIMIT' copies of the handler program are
-       allowed to run at one time. If furter request are received
+       allowed to run at one time. If further requests are received
        while 'LIMIT' children are running, *httrcall* will block,
        waiting for one to exit before processing further requests. If
-       no *-l* option is specified, *httrcall* imposes no limit on
-       the number of children that may run.
+       no *-l* option is specified (or specified as zero), *httrcall*
+       imposes no limit on the number of children that may run.
 
 AUTHOR
 ------
index 86d6ace..a9ebe69 100644 (file)
@@ -92,23 +92,49 @@ rules are recognized:
        'HEADER', the rule never matches. If 'FLAGS' contain the
        character `i`, 'REGEX' is matched case-independently.
 
+*all*::
+
+       The rule always matches.
+
 *default*::
 
-       Matches if and only if no *match* stanza without a *default*
-       rule has matched.
+       Convenient shorthand for an *all* line followed by *priority
+       -10* (see below).
 
 In addition to the rules, a *match* stanza must contain exactly one
-follow-up line specifying the action to take if it matches. Currently,
-only the *handler* action is recognized:
+follow-up line specifying the action to take if it matches. The
+following actions are supported:
 
 *handler* 'HANDLER'::
 
        'HANDLER' must be a named handler as declared by a *child* or
        *fchild* stanza, to which the request is passed.
 
+*reparse*::
+
+       Apply any side-effects as required by the match stanza (such
+       as rest-string or header modifications), and then retry the
+       matching of the request. During the rematching, the stanza
+       containing the *reparse* action will be disabled. Multiple
+       *reparse* stanzas may be used recursively.
+
 Additionally, a *match* stanza may contain any of the following,
 optional lines:
 
+*priority* 'INTEGER'::
+
+       Specifies the priority for the match stanza. In case more than
+       one stanza match a request, the one with the highest priority
+       is used. In case more than one stanza with the same highest
+       priority match a request, it is unspecified which will be
+       used. Match stanzas without a priority specification will
+       default to priority 0. Either positive or negative priorities
+       may be specified.
+
+*order* 'INTEGER'::
+
+       A synonym for *priority*. Use whichever you like the best.
+
 *set* 'HEADER' 'VALUE'::
 
        If the *match* stanza as a whole matches, the named HTTP
index 1269073..db59dd9 100644 (file)
@@ -99,6 +99,8 @@ LOGIN
 
 As part of the login procedure, *userplex* does the following:
 
+ * A new session is entered (with *setsid*(2)).
+
  * The UID, GID and auxiliary groups of the new process are changed
    accordingly. For testing purposes, *userplex* may be running as a
    user other than root, and the child process will, then, simply exit
index 9d46abd..cefd197 100644 (file)
@@ -1,3 +1,4 @@
-include dirplex.d/*.rc
-
 index-file index
+dot-allow .well-known
+
+include dirplex.d/*.rc
index 9fd1e42..d3738a1 100755 (executable)
@@ -15,6 +15,9 @@ set -e
 PATH=/usr/local/bin:/usr/local/sbin:$PATH
 HTPARSER="$(which htparser || true)"
 PIDFILE=/var/run/ashd.pid
+GRACE_PERIOD=10
+USER=nobody
+CHROOT=/var/tmp
 PORTSPEC="plain"
 ROOTSPEC="dirplex /srv/www"
 [ -r /etc/default/locale ] && . /etc/default/locale
@@ -23,15 +26,82 @@ ROOTSPEC="dirplex /srv/www"
 
 start() {
     export LANG
-    log_daemon_msg "Starting HTTP server" "ashd"
-    start-stop-daemon -S -p "$PIDFILE" -qx "$HTPARSER" -- -Sf -p "$PIDFILE" -u nobody -r /var/tmp $PORTSPEC -- $ROOTSPEC
-    log_end_msg $?
+    [ -n "$SILENT_INIT" ] || log_daemon_msg "Starting HTTP server" "ashd"
+    if start-stop-daemon -S -p "$PIDFILE" -qa "$HTPARSER" -- -Sf -p "$PIDFILE" -u "$USER" -r "$CHROOT" $PORTSPEC -- $ROOTSPEC; then
+       [ -n "$SILENT_INIT" ] || log_success_msg
+    else
+       [ -n "$SILENT_INIT" ] || log_end_msg 1
+    fi
 }
 
-stop() {
-    log_daemon_msg "Stopping HTTP server" "ashd"
+kill_wholly() {
     start-stop-daemon -K -p "$PIDFILE" -qx "$HTPARSER"
-    log_end_msg $?
+}
+
+kill_listen() {
+    pid=$(cat "$PIDFILE" 2>/dev/null || true)
+    if [ -z "$pid" ]; then
+       log_failure_msg "no pid file"
+       return 1
+    fi
+    if ! kill -0 "$pid"; then
+       log_failure_msg "invalid saved pid"
+       return 1
+    fi
+    [ -n "$SILENT_INIT" ] || log_progress_msg "listen"
+    kill -TERM "$pid"
+    for try in 0 1 2 3 4 5; do
+         sleep $try
+         case "$(wc -l <"$PIDFILE")" in
+             1) continue ;;
+             0|2) return 0 ;;
+             *)
+                 log_failure_msg "could not parse pid file"
+                 return 1
+                 ;;
+         esac
+    done
+    log_failure_msg "htparser did not stop listening, killing it completely"
+    kill_wholly
+    start-stop-daemon -K -p "$PIDFILE" -qx "$HTPARSER"
+    return 1
+}
+
+stop_listen() {
+    [ -n "$SILENT_INIT" ] || log_daemon_msg "Stopping HTTP server" "ashd"
+    if kill_listen; then
+       [ -n "$SILENT_INIT" ] || log_success_msg
+    else
+       [ -n "$SILENT_INIT" ] || log_end_msg $?
+    fi
+}
+
+stop_gracefully() {
+    [ -n "$SILENT_INIT" ] || log_daemon_msg "Stopping HTTP server" "ashd"
+    if ! kill_listen ; then
+       log_end_msg 1
+       return 1
+    fi
+    pid=$(cat "$PIDFILE" 2>/dev/null || true)
+    if kill -0 "$pid" 2>/dev/null; then
+       [ -n "$SILENT_INIT" ] || log_progress_msg "waiting for remaining connections..."
+       for try in $(seq "$GRACE_PERIOD"); do
+           sleep 1
+           if ! kill -0 "$pid" 2>/dev/null; then
+               [ -n "$SILENT_INIT" ] || log_success_msg
+               return 0
+           fi
+       done
+    else
+       [ -n "$SILENT_INIT" ] || log_success_msg
+       return 0
+    fi
+    [ -n "$SILENT_INIT" ] || log_progress_msg "terminating remaining connections"
+    if kill_wholly; then
+       [ -n "$SILENT_INIT" ] || log_success_msg
+    else
+       log_end_msg 1
+    fi
 }
 
 case "$1" in
@@ -39,10 +109,12 @@ case "$1" in
        start
        ;;
     stop)
-       stop
+       stop_gracefully
        ;;
     restart)
-       stop
+       stop_listen
+       # Truncate PID file to allow start-stop-daemon to work despite remaining connections.
+       >"$PIDFILE"
        start
        ;;
 esac
index 3c21690..a9d419f 100755 (executable)
@@ -6,4 +6,4 @@ cd "$(dirname "$0")"
 
 # Start htparser running patplex; see the patterns.conf file for
 # further details.
-htparser plain:port=8080 -- patplex patterns.conf
+htparser plain:port=8080 -- patplex ./patterns.conf
index e9f92b0..3de5861 100644 (file)
@@ -75,8 +75,10 @@ class handler(object):
     def handle(self, request):
         raise Exception()
     def ckflush(self, req):
+        p = select.poll()
+        p.register(req, select.POLLOUT)
         while len(req.buffer) > 0:
-            rls, wls, els = select.select([], [req], [req])
+            p.poll()
             req.flush()
     def close(self):
         pass
index fa0b8b5..c10bfc9 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/python3
 
-import sys, os, getopt, socket, logging, time, locale, collections, signal
+import sys, os, getopt, socket, logging, time, locale, collections.abc, signal
 import ashd.util, ashd.serve, ashd.htlib
 try:
     import pdm.srv
@@ -148,10 +148,10 @@ def mkenv(req):
     return env
 
 def recode(thing):
-    if isinstance(thing, collections.ByteString):
+    if isinstance(thing, collections.abc.ByteString):
         return thing
     else:
-        return str(thing).encode("latin-1")
+        return str(thing).encode("utf-8")
 
 class request(ashd.serve.wsgirequest):
     def __init__(self, *, bkreq, **kw):
index 238f6b7..b78920f 100644 (file)
@@ -1,300 +1 @@
-import sys, os, errno, threading, select, traceback
-
-class epoller(object):
-    exc_handler = None
-
-    def __init__(self, check=None):
-        self.registered = {}
-        self.fdcache = {}
-        self.lock = threading.RLock()
-        self.ep = None
-        self.th = None
-        self.stopped = False
-        self.loopcheck = set()
-        if check is not None:
-            self.loopcheck.add(check)
-        self._daemon = True
-
-    @staticmethod
-    def _evsfor(ch):
-        return ((select.EPOLLIN if ch.readable else 0) |
-                (select.EPOLLOUT if ch.writable else 0))
-
-    def _ckrun(self):
-        if self.registered and self.th is None:
-            th = threading.Thread(target=self._run, name="Async epoll thread")
-            th.daemon = self._daemon
-            th.start()
-            self.th = th
-
-    def exception(self, ch, *exc):
-        self.remove(ch)
-        if self.exc_handler is None:
-            traceback.print_exception(*exc)
-        else:
-            self.exc_handler(ch, *exc)
-
-    def _cb(self, ch, nm):
-        try:
-            m = getattr(ch, nm, None)
-            if m is None:
-                raise AttributeError("%r has no %s method" % (ch, nm))
-            m()
-        except Exception as exc:
-            self.exception(ch, *sys.exc_info())
-
-    def _closeall(self):
-        with self.lock:
-            while self.registered:
-                fd, (ch, evs) = next(iter(self.registered.items()))
-                del self.registered[fd]
-                self.ep.unregister(fd)
-                self._cb(ch, "close")
-
-    def _run(self):
-        ep = select.epoll()
-        try:
-            with self.lock:
-                try:
-                    for fd, (ob, evs) in self.registered.items():
-                        ep.register(fd, evs)
-                except:
-                    self.registered.clear()
-                    raise
-                self.ep = ep
-
-            while self.registered:
-                for ck in self.loopcheck:
-                    ck(self)
-                if self.stopped:
-                    self._closeall()
-                    break
-                try:
-                    evlist = ep.poll(10)
-                except IOError as exc:
-                    if exc.errno == errno.EINTR:
-                        continue
-                    raise
-                for fd, evs in evlist:
-                    with self.lock:
-                        if fd not in self.registered:
-                            continue
-                        ch, cevs = self.registered[fd]
-                        if fd in self.registered and evs & (select.EPOLLIN | select.EPOLLHUP | select.EPOLLERR):
-                            self._cb(ch, "read")
-                        if fd in self.registered and evs & select.EPOLLOUT:
-                            self._cb(ch, "write")
-                        if fd in self.registered:
-                            nevs = self._evsfor(ch)
-                            if nevs == 0:
-                                del self.fdcache[ch]
-                                del self.registered[fd]
-                                ep.unregister(fd)
-                                self._cb(ch, "close")
-                            elif nevs != cevs:
-                                self.registered[fd] = ch, nevs
-                                ep.modify(fd, nevs)
-
-        finally:
-            with self.lock:
-                self.th = None
-                self.ep = None
-                self._ckrun()
-            ep.close()
-
-    @property
-    def daemon(self): return self._daemon
-    @daemon.setter
-    def daemon(self, value):
-        self._daemon = bool(value)
-        with self.lock:
-            if self.th is not None:
-                self.th = daemon = self._daemon
-
-    def add(self, ch):
-        with self.lock:
-            fd = ch.fileno()
-            if fd in self.registered:
-                raise KeyError("fd %i is already registered" % fd)
-            evs = self._evsfor(ch)
-            if evs == 0:
-                ch.close()
-                return
-            ch.watcher = self
-            self.fdcache[ch] = fd
-            self.registered[fd] = (ch, evs)
-            if self.ep:
-                self.ep.register(fd, evs)
-            self._ckrun()
-
-    def remove(self, ch, ignore=False):
-        with self.lock:
-            try:
-                fd = self.fdcache[ch]
-            except KeyError:
-                if ignore:
-                    return
-                raise KeyError("fd %i is not registered" % fd)
-            pch, cevs = self.registered[fd]
-            if pch is not ch:
-                raise ValueError("fd %i registered via object %r, cannot remove with %r" % (pch, ch))
-            del self.fdcache[ch]
-            del self.registered[fd]
-            if self.ep:
-                self.ep.unregister(fd)
-            ch.close()
-
-    def update(self, ch, ignore=False):
-        with self.lock:
-            try:
-                fd = self.fdcache[ch]
-            except KeyError:
-                if ignore:
-                    return
-                raise KeyError("fd %i is not registered" % fd)
-            pch, cevs = self.registered[fd]
-            if pch is not ch:
-                raise ValueError("fd %i registered via object %r, cannot update with %r" % (pch, ch))
-            evs = self._evsfor(ch)
-            if evs == 0:
-                del self.fdcache[ch]
-                del self.registered[fd]
-                if self.ep:
-                    self.ep.unregister(fd)
-                ch.close()
-            elif evs != cevs:
-                self.registered[fd] = ch, evs
-                if self.ep:
-                    self.ep.modify(fd, evs)
-
-    def stop(self):
-        if threading.current_thread() == self.th:
-            self.stopped = True
-        else:
-            def tgt():
-                self.stopped = True
-            cb = callbuffer()
-            cb.call(tgt)
-            cb.stop()
-            self.add(cb)
-
-def watcher():
-    return epoller()
-
-class channel(object):
-    readable = False
-    writable = False
-
-    def __init__(self):
-        self.watcher = None
-
-    def fileno(self):
-        raise NotImplementedError("fileno()")
-
-    def close(self):
-        pass
-
-class sockbuffer(channel):
-    def __init__(self, socket, **kwargs):
-        super().__init__(**kwargs)
-        self.sk = socket
-        self.eof = False
-        self.obuf = bytearray()
-
-    def fileno(self):
-        return self.sk.fileno()
-
-    def close(self):
-        self.sk.close()
-
-    def gotdata(self, data):
-        if data == b"":
-            self.eof = True
-
-    def send(self, data, eof=False):
-        self.obuf.extend(data)
-        if eof:
-            self.eof = True
-        if self.watcher is not None:
-            self.watcher.update(self, True)
-
-    @property
-    def readable(self):
-        return not self.eof
-    def read(self):
-        try:
-            data = self.sk.recv(1024)
-            self.gotdata(data)
-        except IOError:
-            self.obuf[:] = b""
-            self.eof = True
-
-    @property
-    def writable(self):
-        return bool(self.obuf);
-    def write(self):
-        try:
-            ret = self.sk.send(self.obuf)
-            self.obuf[:ret] = b""
-        except IOError:
-            self.obuf[:] = b""
-            self.eof = True
-
-class callbuffer(channel):
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
-        self.queue = []
-        self.rp, self.wp = os.pipe()
-        self.lock = threading.Lock()
-        self.eof = False
-
-    def fileno(self):
-        return self.rp
-
-    def close(self):
-        with self.lock:
-            try:
-                if self.wp >= 0:
-                    os.close(self.wp)
-                self.wp = -1
-            finally:
-                if self.rp >= 0:
-                    os.close(self.rp)
-                self.rp = -1
-
-    @property
-    def readable(self):
-        return not self.eof
-    def read(self):
-        with self.lock:
-            try:
-                data = os.read(self.rp, 1024)
-                if data == b"":
-                    self.eof = True
-            except IOError:
-                self.eof = True
-            cbs = list(self.queue)
-            self.queue[:] = []
-        for cb in cbs:
-            cb()
-
-    writable = False
-
-    def call(self, cb):
-        with self.lock:
-            if self.wp < 0:
-                raise Exception("stopped")
-            self.queue.append(cb)
-            os.write(self.wp, b"a")
-
-    def stop(self):
-        with self.lock:
-            if self.wp >= 0:
-                os.close(self.wp)
-                self.wp = -1
-
-def currentwatcher(io, current):
-    def check(io):
-        if not current:
-            io.stop()
-    io.loopcheck.add(check)
+from .asyncio import *
diff --git a/python3/ashd/asyncio.py b/python3/ashd/asyncio.py
new file mode 100644 (file)
index 0000000..238f6b7
--- /dev/null
@@ -0,0 +1,300 @@
+import sys, os, errno, threading, select, traceback
+
+class epoller(object):
+    exc_handler = None
+
+    def __init__(self, check=None):
+        self.registered = {}
+        self.fdcache = {}
+        self.lock = threading.RLock()
+        self.ep = None
+        self.th = None
+        self.stopped = False
+        self.loopcheck = set()
+        if check is not None:
+            self.loopcheck.add(check)
+        self._daemon = True
+
+    @staticmethod
+    def _evsfor(ch):
+        return ((select.EPOLLIN if ch.readable else 0) |
+                (select.EPOLLOUT if ch.writable else 0))
+
+    def _ckrun(self):
+        if self.registered and self.th is None:
+            th = threading.Thread(target=self._run, name="Async epoll thread")
+            th.daemon = self._daemon
+            th.start()
+            self.th = th
+
+    def exception(self, ch, *exc):
+        self.remove(ch)
+        if self.exc_handler is None:
+            traceback.print_exception(*exc)
+        else:
+            self.exc_handler(ch, *exc)
+
+    def _cb(self, ch, nm):
+        try:
+            m = getattr(ch, nm, None)
+            if m is None:
+                raise AttributeError("%r has no %s method" % (ch, nm))
+            m()
+        except Exception as exc:
+            self.exception(ch, *sys.exc_info())
+
+    def _closeall(self):
+        with self.lock:
+            while self.registered:
+                fd, (ch, evs) = next(iter(self.registered.items()))
+                del self.registered[fd]
+                self.ep.unregister(fd)
+                self._cb(ch, "close")
+
+    def _run(self):
+        ep = select.epoll()
+        try:
+            with self.lock:
+                try:
+                    for fd, (ob, evs) in self.registered.items():
+                        ep.register(fd, evs)
+                except:
+                    self.registered.clear()
+                    raise
+                self.ep = ep
+
+            while self.registered:
+                for ck in self.loopcheck:
+                    ck(self)
+                if self.stopped:
+                    self._closeall()
+                    break
+                try:
+                    evlist = ep.poll(10)
+                except IOError as exc:
+                    if exc.errno == errno.EINTR:
+                        continue
+                    raise
+                for fd, evs in evlist:
+                    with self.lock:
+                        if fd not in self.registered:
+                            continue
+                        ch, cevs = self.registered[fd]
+                        if fd in self.registered and evs & (select.EPOLLIN | select.EPOLLHUP | select.EPOLLERR):
+                            self._cb(ch, "read")
+                        if fd in self.registered and evs & select.EPOLLOUT:
+                            self._cb(ch, "write")
+                        if fd in self.registered:
+                            nevs = self._evsfor(ch)
+                            if nevs == 0:
+                                del self.fdcache[ch]
+                                del self.registered[fd]
+                                ep.unregister(fd)
+                                self._cb(ch, "close")
+                            elif nevs != cevs:
+                                self.registered[fd] = ch, nevs
+                                ep.modify(fd, nevs)
+
+        finally:
+            with self.lock:
+                self.th = None
+                self.ep = None
+                self._ckrun()
+            ep.close()
+
+    @property
+    def daemon(self): return self._daemon
+    @daemon.setter
+    def daemon(self, value):
+        self._daemon = bool(value)
+        with self.lock:
+            if self.th is not None:
+                self.th = daemon = self._daemon
+
+    def add(self, ch):
+        with self.lock:
+            fd = ch.fileno()
+            if fd in self.registered:
+                raise KeyError("fd %i is already registered" % fd)
+            evs = self._evsfor(ch)
+            if evs == 0:
+                ch.close()
+                return
+            ch.watcher = self
+            self.fdcache[ch] = fd
+            self.registered[fd] = (ch, evs)
+            if self.ep:
+                self.ep.register(fd, evs)
+            self._ckrun()
+
+    def remove(self, ch, ignore=False):
+        with self.lock:
+            try:
+                fd = self.fdcache[ch]
+            except KeyError:
+                if ignore:
+                    return
+                raise KeyError("fd %i is not registered" % fd)
+            pch, cevs = self.registered[fd]
+            if pch is not ch:
+                raise ValueError("fd %i registered via object %r, cannot remove with %r" % (pch, ch))
+            del self.fdcache[ch]
+            del self.registered[fd]
+            if self.ep:
+                self.ep.unregister(fd)
+            ch.close()
+
+    def update(self, ch, ignore=False):
+        with self.lock:
+            try:
+                fd = self.fdcache[ch]
+            except KeyError:
+                if ignore:
+                    return
+                raise KeyError("fd %i is not registered" % fd)
+            pch, cevs = self.registered[fd]
+            if pch is not ch:
+                raise ValueError("fd %i registered via object %r, cannot update with %r" % (pch, ch))
+            evs = self._evsfor(ch)
+            if evs == 0:
+                del self.fdcache[ch]
+                del self.registered[fd]
+                if self.ep:
+                    self.ep.unregister(fd)
+                ch.close()
+            elif evs != cevs:
+                self.registered[fd] = ch, evs
+                if self.ep:
+                    self.ep.modify(fd, evs)
+
+    def stop(self):
+        if threading.current_thread() == self.th:
+            self.stopped = True
+        else:
+            def tgt():
+                self.stopped = True
+            cb = callbuffer()
+            cb.call(tgt)
+            cb.stop()
+            self.add(cb)
+
+def watcher():
+    return epoller()
+
+class channel(object):
+    readable = False
+    writable = False
+
+    def __init__(self):
+        self.watcher = None
+
+    def fileno(self):
+        raise NotImplementedError("fileno()")
+
+    def close(self):
+        pass
+
+class sockbuffer(channel):
+    def __init__(self, socket, **kwargs):
+        super().__init__(**kwargs)
+        self.sk = socket
+        self.eof = False
+        self.obuf = bytearray()
+
+    def fileno(self):
+        return self.sk.fileno()
+
+    def close(self):
+        self.sk.close()
+
+    def gotdata(self, data):
+        if data == b"":
+            self.eof = True
+
+    def send(self, data, eof=False):
+        self.obuf.extend(data)
+        if eof:
+            self.eof = True
+        if self.watcher is not None:
+            self.watcher.update(self, True)
+
+    @property
+    def readable(self):
+        return not self.eof
+    def read(self):
+        try:
+            data = self.sk.recv(1024)
+            self.gotdata(data)
+        except IOError:
+            self.obuf[:] = b""
+            self.eof = True
+
+    @property
+    def writable(self):
+        return bool(self.obuf);
+    def write(self):
+        try:
+            ret = self.sk.send(self.obuf)
+            self.obuf[:ret] = b""
+        except IOError:
+            self.obuf[:] = b""
+            self.eof = True
+
+class callbuffer(channel):
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        self.queue = []
+        self.rp, self.wp = os.pipe()
+        self.lock = threading.Lock()
+        self.eof = False
+
+    def fileno(self):
+        return self.rp
+
+    def close(self):
+        with self.lock:
+            try:
+                if self.wp >= 0:
+                    os.close(self.wp)
+                self.wp = -1
+            finally:
+                if self.rp >= 0:
+                    os.close(self.rp)
+                self.rp = -1
+
+    @property
+    def readable(self):
+        return not self.eof
+    def read(self):
+        with self.lock:
+            try:
+                data = os.read(self.rp, 1024)
+                if data == b"":
+                    self.eof = True
+            except IOError:
+                self.eof = True
+            cbs = list(self.queue)
+            self.queue[:] = []
+        for cb in cbs:
+            cb()
+
+    writable = False
+
+    def call(self, cb):
+        with self.lock:
+            if self.wp < 0:
+                raise Exception("stopped")
+            self.queue.append(cb)
+            os.write(self.wp, b"a")
+
+    def stop(self):
+        with self.lock:
+            if self.wp >= 0:
+                os.close(self.wp)
+                self.wp = -1
+
+def currentwatcher(io, current):
+    def check(io):
+        if not current:
+            io.stop()
+    io.loopcheck.add(check)
index 87f60a0..0927710 100644 (file)
@@ -75,8 +75,10 @@ class handler(object):
     def handle(self, request):
         raise Exception()
     def ckflush(self, req):
+        p = select.poll()
+        p.register(req, select.POLLOUT)
         while len(req.buffer) > 0:
-            rls, wls, els = select.select([], [req], [req])
+            p.poll()
             req.flush()
     def close(self):
         pass
index 3818e4b..bf32637 100644 (file)
@@ -161,7 +161,10 @@ def serveloop(handler, sock = 0):
     and is called once for each received request.
     """
     while True:
-        req = proto.recvreq(sock)
+        try:
+            req = proto.recvreq(sock)
+        except InterruptedError:
+            continue
         if req is None:
             break
         try:
index 1ecc66e..9bed7b2 100644 (file)
@@ -13,3 +13,5 @@
 /psendfile
 /httimed
 /httrcall
+/htpipe
+/ratequeue
index 5422e74..3bc8963 100644 (file)
@@ -2,14 +2,14 @@ SUBDIRS = dirplex
 
 bin_PROGRAMS = htparser sendfile callcgi patplex userplex htls \
                callscgi accesslog htextauth callfcgi multifscgi \
-               errlogger httimed psendfile httrcall
+               errlogger httimed psendfile httrcall htpipe ratequeue
 
-htparser_SOURCES = htparser.c htparser.h plaintcp.c ssl-gnutls.c
+htparser_SOURCES = htparser.c htparser.h plaintcp.c ssl-gnutls.c ssl-openssl.c
 
 LDADD = $(top_srcdir)/lib/libht.a
 AM_CPPFLAGS = -I$(top_srcdir)/lib
 
-htparser_CPPFLAGS = $(AM_CPPFLAGS) @GNUTLS_CPPFLAGS@
-htparser_LDADD = $(LDADD) @GNUTLS_LIBS@
+htparser_CPPFLAGS = $(AM_CPPFLAGS) @GNUTLS_CPPFLAGS@ @OPENSSL_CPPFLAGS@
+htparser_LDADD = $(LDADD) @GNUTLS_LIBS@ @OPENSSL_LIBS@
 sendfile_LDADD = $(LDADD) -lmagic @XATTR_LIBS@
 psendfile_LDADD = $(LDADD) -lmagic @XATTR_LIBS@
index af43373..0c7ef8d 100644 (file)
@@ -96,6 +96,16 @@ static void logitem(struct logdata *data, char o, char *d)
            qputs(h, out);
        }
        break;
+    case 'p':
+       if(!data->resp || ((h = getheader(data->resp, d)) == NULL)) {
+           putc('-', out);
+       } else {
+           qputs(h, out);
+       }
+       break;
+    case 'P':
+       logitem(data, 'p', sprintf3("X-Ash-%s", d));
+       break;
     case 'u':
        qputs(data->req->url, out);
        break;
@@ -508,7 +518,7 @@ int main(int argc, char **argv)
            pidfile = optarg;
            break;
        case 'a':
-           format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" %c %o \"%R\" \"%G\"";
+           format = "%A - %{log-user}P [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" %c %o \"%R\" \"%G\"";
            break;
        default:
            usage(stderr);
index 9ae4ca7..0bfa6f4 100644 (file)
@@ -84,6 +84,7 @@ static void freeconfig(struct config *cf)
        freepattern(pat);
     }
     freeca(cf->index);
+    freeca(cf->dotallow);
     if(cf->capture != NULL)
        free(cf->capture);
     if(cf->reparse != NULL)
@@ -258,6 +259,9 @@ struct config *readconfig(char *file)
        } else if(!strcmp(s->argv[0], "index-file")) {
            freeca(cf->index);
            cf->index = cadup(s->argv + 1);
+       } else if(!strcmp(s->argv[0], "dot-allow")) {
+           freeca(cf->dotallow);
+           cf->dotallow = cadup(s->argv + 1);
        } else if(!strcmp(s->argv[0], "capture")) {
            if(s->argc < 2) {
                flog(LOG_WARNING, "%s:%i: missing argument to capture declaration", s->file, s->lno);
index 73bb9cc..ee7f650 100644 (file)
@@ -25,6 +25,7 @@
 #include <ctype.h>
 #include <dirent.h>
 #include <time.h>
+#include <fnmatch.h>
 #include <sys/wait.h>
 #include <sys/signal.h>
 
@@ -131,6 +132,27 @@ static void handlefile(struct hthead *req, int fd, char *path)
     handle(req, fd, path, pat);
 }
 
+static int checkaccess(char *path, char *name)
+{
+    int i, o;
+    struct config **cfs;
+    
+    if(*name == '.') {
+       cfs = getconfigs(sprintf3("%s/", path));
+       for(i = 0; cfs[i] != NULL; i++) {
+           if(cfs[i]->dotallow != NULL) {
+               for(o = 0; cfs[i]->dotallow[o] != NULL; o++) {
+                   if(!fnmatch(cfs[i]->dotallow[o], name, 0))
+                       return(1);
+               }
+               break;
+           }
+       }
+       return(0);
+    }
+    return(1);
+}
+
 static char *findfile(char *path, char *name, struct stat *sb)
 {
     DIR *dir;
@@ -155,12 +177,20 @@ static char *findfile(char *path, char *name, struct stat *sb)
            continue;
        if(strncmp(dent->d_name, name, strlen(name)))
            continue;
-       fp = sprintf3("%s/%s", path, dent->d_name);
-       if(stat(fp, sb))
+       fp = sprintf2("%s/%s", path, dent->d_name);
+       if(stat(fp, sb)) {
+           free(fp);
+           continue;
+       }
+       if(!S_ISREG(sb->st_mode)) {
+           free(fp);
            continue;
-       if(!S_ISREG(sb->st_mode))
+       }
+       if(!checkaccess(path, dent->d_name)) {
+           free(fp);
            continue;
-       ret = sstrdup(fp);
+       }
+       ret = fp;
        break;
     }
     closedir(dir);
@@ -216,9 +246,9 @@ static int checkentry(struct hthead *req, int fd, char *path, char *rest, char *
     char *newpath;
     int rv;
     
-    if(*el == '.')
-       return(0);
     if(!stat(sprintf3("%s/%s", path, el), &sb)) {
+       if(!checkaccess(path, el))
+           return(0);
        if(S_ISDIR(sb.st_mode)) {
            if(!*rest) {
                stdredir(req, fd, 301, sprintf3("%s/", el));
index ffe12a6..754327d 100644 (file)
@@ -17,7 +17,7 @@ struct config {
     time_t mtime, lastck;
     struct child *children;
     struct pattern *patterns;
-    char **index;
+    char **index, **dotallow;
     char *capture, *reparse;
     int caproot, parsecomb;
 };
index d10121c..5491855 100644 (file)
@@ -40,7 +40,6 @@
 #include "htparser.h"
 
 static int plex;
-static char *pidfile = NULL;
 static int daemonize, usesyslog;
 struct mtbuf listeners;
 
@@ -581,9 +580,12 @@ static void addport(char *spec)
     /* XXX: It would be nice to decentralize this, but, meh... */
     if(!strcmp(nm, "plain")) {
        handleplain(pars.d, pars.b, vals.b);
-#ifdef HAVE_GNUTLS
+#if defined HAVE_GNUTLS
     } else if(!strcmp(nm, "ssl")) {
        handlegnussl(pars.d, pars.b, vals.b);
+#elif defined HAVE_OPENSSL
+    } else if(!strcmp(nm, "ssl")) {
+       handleossl(pars.d, pars.b, vals.b);
 #endif
     } else {
        flog(LOG_ERR, "htparser: unknown port handler `%s'", nm);
@@ -603,12 +605,12 @@ int main(int argc, char **argv)
 {
     int c, d;
     int i, s1;
-    char *root;
+    char *root, *pidfile, *pidtmp;
     FILE *pidout;
     struct passwd *pwent;
     
     daemonize = usesyslog = 0;
-    root = NULL;
+    root = pidfile = NULL;
     pwent = NULL;
     while((c = getopt(argc, argv, "+hSfu:r:p:")) >= 0) {
        switch(c) {
@@ -622,16 +624,16 @@ int main(int argc, char **argv)
            usesyslog = 1;
            break;
        case 'u':
-           if((pwent = getpwnam(optarg)) == NULL) {
+           if(optarg[0] && ((pwent = getpwnam(optarg)) == NULL)) {
                flog(LOG_ERR, "could not find user %s", optarg);
                exit(1);
            }
            break;
        case 'r':
-           root = optarg;
+           root = optarg[0] ? optarg : NULL;
            break;
        case 'p':
-           pidfile = optarg;
+           pidfile = optarg[0] ? optarg : NULL;
            break;
        default:
            usage(stderr);
@@ -656,8 +658,14 @@ int main(int argc, char **argv)
     bufadd(listeners, mustart(plexwatch, plex));
     pidout = NULL;
     if(pidfile != NULL) {
-       if((pidout = fopen(pidfile, "w")) == NULL) {
-           flog(LOG_ERR, "could not open %s for writing: %s", pidfile, strerror(errno));
+       pidtmp = sprintf3("%s.new", pidfile);
+       if((pidout = fopen(pidtmp, "w")) == NULL) {
+           flog(LOG_ERR, "could not open %s for writing: %s", pidtmp, strerror(errno));
+           return(1);
+       }
+       if(rename(pidtmp, pidfile)) {
+           flog(LOG_ERR, "could not overwrite %s: %s", pidfile, strerror(errno));
+           unlink(pidtmp);
            return(1);
        }
     }
@@ -688,7 +696,7 @@ int main(int argc, char **argv)
     }
     if(pidout != NULL) {
        fprintf(pidout, "%i\n", getpid());
-       fclose(pidout);
+       fflush(pidout);
     }
     d = 0;
     while(!d) {
@@ -701,11 +709,17 @@ int main(int argc, char **argv)
                while(listeners.d > 0)
                    resume(listeners.b[0], 0);
                flog(LOG_INFO, "no longer listening");
+               if(pidout != NULL) {
+                   putc('\n', pidout);
+                   fflush(pidout);
+               }
            } else {
                d = 1;
            }
            break;
        }
     }
+    if(pidout != NULL)
+       ftruncate(fileno(pidout), 0);
     return(0);
 }
index d9f014d..946ed51 100644 (file)
@@ -20,6 +20,9 @@ void handleplain(int argc, char **argp, char **argv);
 #ifdef HAVE_GNUTLS
 void handlegnussl(int argc, char **argp, char **argv);
 #endif
+#ifdef HAVE_OPENSSL
+void handleossl(int argc, char **argp, char **argv);
+#endif
 
 extern struct mtbuf listeners;
 
diff --git a/src/htpipe.c b/src/htpipe.c
new file mode 100644 (file)
index 0000000..02cbd35
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+    ashd - A Sane HTTP Daemon
+    Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <utils.h>
+#include <log.h>
+#include <req.h>
+#include <proc.h>
+
+static void usage(FILE *out)
+{
+    fprintf(out, "usge: htpipe [-h] [-CS] SOCKET-PATH [CHILD ARGS...]\n");
+}
+
+static int clconnect(char *path)
+{
+    int sk;
+    struct sockaddr_un unm;
+    
+    if((sk = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0)
+       return(-1);
+    memset(&unm, 0, sizeof(unm));
+    unm.sun_family = AF_UNIX;
+    strcpy(unm.sun_path, path);
+    if(connect(sk, (struct sockaddr *)&unm, sizeof(unm))) {
+       close(sk);
+       return(-1);
+    }
+    return(sk);
+}
+
+static int mklisten(char *path)
+{
+    int sk;
+    struct sockaddr_un unm;
+    struct stat sb;
+    
+    if(!stat(path, &sb) && S_ISSOCK(sb.st_mode))
+       unlink(path);
+    if((sk = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0)
+       return(-1);
+    memset(&unm, 0, sizeof(unm));
+    unm.sun_family = AF_UNIX;
+    strcpy(unm.sun_path, path);
+    if(bind(sk, (struct sockaddr *)&unm, sizeof(unm)) || listen(sk, 128)) {
+       close(sk);
+       return(-1);
+    }
+    return(sk);
+}
+
+static void runclient(int sk)
+{
+    int fd;
+    struct hthead *req;
+    
+    while(1) {
+       if((fd = recvreq(0, &req)) < 0) {
+           if(errno == 0)
+               break;
+           flog(LOG_ERR, "htpipe: error in recvreq: %s", strerror(errno));
+           exit(1);
+       }
+       if(sendreq(sk, req, fd)) {
+           flog(LOG_ERR, "htpipe: could not pass request across pipe: %s", strerror(errno));
+           exit(1);
+       }
+       freehthead(req);
+       close(fd);
+    }
+}
+
+static void runserver(int lsk, int ch)
+{
+    int i, o, ret, rfd, ncl, *cl, acl;
+    struct hthead *req;
+    
+    ncl = 0;
+    cl = NULL;
+    while(1) {
+       struct pollfd pfd[ncl + 1];
+       for(i = 0; i < ncl; i++) {
+           pfd[i].fd = cl[i];
+           pfd[i].events= POLLIN;
+       }
+       pfd[i].fd = lsk;
+       pfd[i].events = POLLIN;
+       if((ret = poll(pfd, ncl + 1, -1)) < 0) {
+           if(errno != EINTR) {
+               flog(LOG_ERR, "htpipe: error in poll: %s", strerror(errno));
+               exit(1);
+           }
+       }
+       for(i = 0; i < ncl; i++) {
+           if(pfd[i].revents & POLLIN) {
+               if((rfd = recvreq(cl[i], &req)) < 0) {
+                   if(errno != 0)
+                       flog(LOG_ERR, "htpipe: error from client: %s", strerror(errno));
+                   close(cl[i]);
+                   cl[i] = -1;
+               } else {
+                   if(sendreq(ch, req, rfd)) {
+                       flog(LOG_ERR, "htpipe: could not pass request to child: %s", strerror(errno));
+                       exit(1);
+                   }
+                   freehthead(req);
+                   close(rfd);
+               }
+           }
+       }
+       if(pfd[i].revents & POLLIN) {
+           if((acl = accept(lsk, NULL, 0)) < 0) {
+               flog(LOG_ERR, "htpipe: error in accept: %s", strerror(errno));
+           } else {
+               cl = srealloc(cl, sizeof(*cl) * (ncl + 1));
+               cl[ncl++] = acl;
+           }
+       }
+       for(i = o = 0; i < ncl; i++) {
+           if(cl[i] >= 0)
+               cl[o++] = cl[i];
+       }
+       ncl = o;
+    }
+}
+
+int main(int argc, char **argv)
+{
+    int c, cli, srv, sk, ch, sst;
+    pid_t sproc;
+    char *path, **chspec;
+    
+    cli = srv = 0;
+    while((c = getopt(argc, argv, "+hCS")) >= 0) {
+       switch(c) {
+       case 'h':
+           usage(stdout);
+           exit(0);
+       case 'C':
+           cli = 1;
+           break;
+       case 'S':
+           srv = 1;
+           break;
+       }
+    }
+    if(argc - optind < 1) {
+       usage(stderr);
+       exit(1);
+    }
+    path = argv[optind++];
+    chspec = argv + optind;
+    if(cli) {
+       if((sk = clconnect(path)) < 0) {
+           flog(LOG_ERR, "htpipe: %s: %s", path, strerror(errno));
+           exit(1);
+       }
+       runclient(sk);
+    } else if(srv) {
+       if(!*chspec) {
+           usage(stderr);
+           exit(1);
+       }
+       if((sk = mklisten(path)) < 0) {
+           flog(LOG_ERR, "htpipe: %s: %s", path, strerror(errno));
+           exit(1);
+       }
+       if((ch = stdmkchild(chspec, NULL, NULL)) < 0) {
+           flog(LOG_ERR, "htpipe: could not fork child: %s", strerror(errno));
+           exit(1);
+       }
+       runserver(sk, ch);
+    } else {
+       if(!*chspec) {
+           usage(stderr);
+           exit(1);
+       }
+       if((sk = clconnect(path)) < 0) {
+           if((sproc = fork()) < 0)
+               err(1, "fork");
+           if(sproc == 0) {
+               if((sk = mklisten(path)) < 0) {
+                   flog(LOG_ERR, "htpipe: %s: %s", path, strerror(errno));
+                   exit(1);
+               }
+               if((ch = stdmkchild(chspec, NULL, NULL)) < 0) {
+                   flog(LOG_ERR, "htpipe: could not fork child: %s", strerror(errno));
+                   exit(1);
+               }
+               daemon(0, 1);
+               runserver(sk, ch);
+               abort();
+           }
+           if((waitpid(sproc, &sst, 0)) != sproc) {
+               flog(LOG_ERR, "htpipe: could not wait for server process: %s", strerror(errno));
+               exit(1);
+           }
+           if((sk = clconnect(path)) < 0) {
+               flog(LOG_ERR, "htpipe: could not connect to newly forked server: %s", strerror(errno));
+               exit(1);
+           }
+       }
+       runclient(sk);
+    }
+    return(0);
+}
index 9939543..e893bc0 100644 (file)
@@ -24,6 +24,7 @@
 #include <errno.h>
 #include <ctype.h>
 #include <regex.h>
+#include <limits.h>
 #include <sys/wait.h>
 
 #ifdef HAVE_CONFIG_H
 #define PAT_METHOD 2
 #define PAT_HEADER 3
 #define PAT_ALL 4
-#define PAT_DEFAULT 5
 
 #define PATFL_MSS 1
 #define PATFL_UNQ 2
 
+#define HND_CHILD 1
+#define HND_REPARSE 2
+
 struct config {
     struct child *children;
     struct pattern *patterns;
@@ -69,6 +72,7 @@ struct pattern {
     char *childnm;
     struct rule **rules;
     char *restpat;
+    int handler, prio, disable;
 };
 
 static struct config *gconfig, *lconfig;
@@ -237,7 +241,14 @@ static struct pattern *parsepattern(struct cfstate *s)
        } else if(!strcmp(s->argv[0], "all")) {
            newrule(pat)->type = PAT_ALL;
        } else if(!strcmp(s->argv[0], "default")) {
-           newrule(pat)->type = PAT_DEFAULT;
+           newrule(pat)->type = PAT_ALL;
+           pat->prio = -10;
+       } else if(!strcmp(s->argv[0], "order") || !strcmp(s->argv[0], "priority")) {
+           if(s->argc < 2) {
+               flog(LOG_WARNING, "%s:%i: missing specification for `%s' directive", s->file, s->lno, s->argv[0]);
+               continue;
+           }
+           pat->prio = atoi(s->argv[1]);
        } else if(!strcmp(s->argv[0], "handler")) {
            if(s->argc < 2) {
                flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", s->file, s->lno);
@@ -246,6 +257,9 @@ static struct pattern *parsepattern(struct cfstate *s)
            if(pat->childnm != NULL)
                free(pat->childnm);
            pat->childnm = sstrdup(s->argv[1]);
+           pat->handler = HND_CHILD;
+       } else if(!strcmp(s->argv[0], "reparse")) {
+           pat->handler = HND_REPARSE;
        } else if(!strcmp(s->argv[0], "restpat")) {
            if(s->argc < 2) {
                flog(LOG_WARNING, "%s:%i: missing pattern for `restpat' directive", s->file, s->lno);
@@ -279,7 +293,7 @@ static struct pattern *parsepattern(struct cfstate *s)
        freepattern(pat);
        return(NULL);
     }
-    if(pat->childnm == NULL) {
+    if(pat->handler == 0) {
        flog(LOG_WARNING, "%s:%i: missing handler in match declaration", s->file, sl);
        freepattern(pat);
        return(NULL);
@@ -394,7 +408,19 @@ static void qoffsets(char *buf, int *obuf, char *pstr, int unquote)
     }
 }
 
-static struct pattern *findmatch(struct config *cf, struct hthead *req, int trydefault)
+struct match {
+    struct pattern *pat;
+    char **mstr;
+    int rmo;
+};
+
+static void freematch(struct match *match)
+{
+    freeca(match->mstr);
+    free(match);
+}
+
+static struct match *findmatch(struct config *cf, struct hthead *req, struct match *match)
 {
     int i, o;
     struct pattern *pat;
@@ -407,6 +433,8 @@ static struct pattern *findmatch(struct config *cf, struct hthead *req, int tryd
     
     mstr = NULL;
     for(pat = cf->patterns; pat != NULL; pat = pat->next) {
+       if(pat->disable || (match && (pat->prio <= match->pat->prio)))
+           continue;
        rmo = -1;
        for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
            rx = NULL;
@@ -451,27 +479,32 @@ static struct pattern *findmatch(struct config *cf, struct hthead *req, int tryd
                    }
                }
            } else if(rule->type == PAT_ALL) {
-           } else if(rule->type == PAT_DEFAULT) {
-               if(!trydefault)
-                   break;
            }
        }
        if(!rule) {
-           if(pat->restpat) {
-               exprestpat(req, pat, mstr);
-           } else if(rmo != -1) {
-               replrest(req, req->rest + rmo);
-           }
-           if(mstr)
-               freeca(mstr);
-           return(pat);
-       }
-       if(mstr) {
-           freeca(mstr);
-           mstr = NULL;
+           if(match)
+               freematch(match);
+           omalloc(match);
+           match->pat = pat;
+           match->mstr = mstr;
+           match->rmo = rmo;
        }
     }
-    return(NULL);
+    return(match);
+}
+
+static void execmatch(struct hthead *req, struct match *match)
+{
+    struct headmod *head;
+    
+    if(match->pat->restpat)
+       exprestpat(req, match->pat, match->mstr);
+    else if(match->rmo != -1)
+       replrest(req, req->rest + match->rmo);
+    for(head = match->pat->headers; head != NULL; head = head->next) {
+       headrmheader(req, head->name);
+       headappheader(req, head->name, head->value);
+    }
 }
 
 static void childerror(struct hthead *req, int fd)
@@ -484,44 +517,42 @@ static void childerror(struct hthead *req, int fd)
 
 static void serve(struct hthead *req, int fd)
 {
-    struct pattern *pat;
-    struct headmod *head;
+    struct match *match;
     struct child *ch;
     
-    pat = NULL;
-    if(pat == NULL)
-       pat = findmatch(lconfig, req, 0);
-    if(pat == NULL)
-       pat = findmatch(lconfig, req, 1);
-    if(gconfig != NULL) {
-       if(pat == NULL)
-           pat = findmatch(gconfig, req, 0);
-       if(pat == NULL)
-           pat = findmatch(gconfig, req, 1);
-    }
-    if(pat == NULL) {
+    match = NULL;
+    match = findmatch(lconfig, req, match);
+    if(gconfig != NULL)
+       match = findmatch(gconfig, req, match);
+    if(match == NULL) {
        simpleerror(fd, 404, "Not Found", "The requested resource could not be found on this server.");
        return;
     }
-    ch = NULL;
-    if(ch == NULL)
-       ch = getchild(lconfig, pat->childnm);
-    if(gconfig != NULL) {
+    execmatch(req, match);
+    switch(match->pat->handler) {
+    case HND_CHILD:
+       ch = NULL;
        if(ch == NULL)
-           ch = getchild(gconfig, pat->childnm);
-    }
-    if(ch == NULL) {
-       flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
-       simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
-       return;
-    }
-    
-    for(head = pat->headers; head != NULL; head = head->next) {
-       headrmheader(req, head->name);
-       headappheader(req, head->name, head->value);
+           ch = getchild(lconfig, match->pat->childnm);
+       if((ch == NULL) && (gconfig != NULL))
+           ch = getchild(gconfig, match->pat->childnm);
+       if(ch == NULL) {
+           flog(LOG_ERR, "child %s requested, but was not declared", match->pat->childnm);
+           simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", match->pat->childnm);
+           break;
+       }
+       if(childhandle(ch, req, fd, NULL, NULL))
+           childerror(req, fd);
+       break;
+    case HND_REPARSE:
+       match->pat->disable = 1;
+       serve(req, fd);
+       match->pat->disable = 0;
+       break;
+    default:
+       abort();
     }
-    if(childhandle(ch, req, fd, NULL, NULL))
-       childerror(req, fd);
+    freematch(match);
 }
 
 static void reloadconf(char *nm)
diff --git a/src/ratequeue.c b/src/ratequeue.c
new file mode 100644 (file)
index 0000000..21d6ec3
--- /dev/null
@@ -0,0 +1,538 @@
+/*
+    ashd - A Sane HTTP Daemon
+    Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <signal.h>
+#include <assert.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <utils.h>
+#include <log.h>
+#include <req.h>
+#include <resp.h>
+#include <proc.h>
+#include <cf.h>
+
+#define SBUCKETS 7
+
+struct source {
+    int type;
+    char data[16];
+    unsigned int len, hash;
+};
+
+struct waiting {
+    struct hthead *req;
+    int fd;
+};
+
+struct bucket {
+    struct source id;
+    double level, last, etime, wtime;
+    typedbuf(struct waiting) brim;
+    int thpos, blocked;
+};
+
+struct btime {
+    struct bucket *bk;
+    double tm;
+};
+
+struct config {
+    double size, rate, retain, warnrate;
+    int brimsize;
+};
+
+static struct bucket *sbuckets[1 << SBUCKETS];
+static struct bucket **buckets = sbuckets;
+static int hashlen = SBUCKETS, nbuckets = 0;
+static typedbuf(struct btime) timeheap;
+static int child, reload;
+static double now;
+static const struct config defcfg = {
+    .size = 100, .rate = 10, .warnrate = 60,
+    .retain = 10, .brimsize = 10,
+};
+static struct config cf;
+
+static double rtime(void)
+{
+    static int init = 0;
+    static struct timespec or;
+    struct timespec ts;
+    
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    if(!init) {
+       or = ts;
+       init = 1;
+    }
+    return((ts.tv_sec - or.tv_sec) + ((ts.tv_nsec - or.tv_nsec) / 1000000000.0));
+}
+
+static struct source reqsource(struct hthead *req)
+{
+    int i;
+    char *sa;
+    struct in_addr a4;
+    struct in6_addr a6;
+    struct source ret;
+    
+    ret = (struct source){};
+    if((sa = getheader(req, "X-Ash-Address")) != NULL) {
+       if(inet_pton(AF_INET, sa, &a4) == 1) {
+           ret.type = AF_INET;
+           memcpy(ret.data, &a4, ret.len = sizeof(a4));
+       } else if(inet_pton(AF_INET6, sa, &a6) == 1) {
+           ret.type = AF_INET6;
+           memcpy(ret.data, &a6, ret.len = sizeof(a6));
+       }
+    }
+    for(i = 0, ret.hash = ret.type; i < ret.len; i++)
+       ret.hash = (ret.hash * 31) + ret.data[i];
+    return(ret);
+}
+
+static int srccmp(const struct source *a, const struct source *b)
+{
+    int c;
+    
+    if((c = a->len - b->len) != 0)
+       return(c);
+    if((c = a->type - b->type) != 0)
+       return(c);
+    return(memcmp(a->data, b->data, a->len));
+}
+
+static const char *formatsrc(const struct source *src)
+{
+    static char buf[128];
+    struct in_addr a4;
+    struct in6_addr a6;
+    
+    switch(src->type) {
+    case AF_INET:
+       memcpy(&a4, src->data, sizeof(a4));
+       if(!inet_ntop(AF_INET, &a4, buf, sizeof(buf)))
+           return("<invalid ipv4>");
+       return(buf);
+    case AF_INET6:
+       memcpy(&a6, src->data, sizeof(a6));
+       if(!inet_ntop(AF_INET6, &a6, buf, sizeof(buf)))
+           return("<invalid ipv6>");
+       return(buf);
+    default:
+       return("<invalid source record>");
+    }
+}
+
+static void rehash(int nlen)
+{
+    unsigned int i, o, n, m, pl, nl;
+    struct bucket **new, **old;
+    
+    old = buckets;
+    if(nlen <= SBUCKETS) {
+       nlen = SBUCKETS;
+       new = sbuckets;
+    } else {
+       new = szmalloc(sizeof(*new) * (1 << nlen));
+    }
+    if(nlen == hashlen)
+       return;
+    assert(old != new);
+    pl = 1 << hashlen; nl = 1 << nlen; m = nl - 1;
+    for(i = 0; i < pl; i++) {
+       if(!old[i])
+           continue;
+       for(o = old[i]->id.hash & m, n = 0; n < nl; o = (o + 1) & m, n++) {
+           if(!new[o]) {
+               new[o] = old[i];
+               break;
+           }
+       }
+    }
+    if(old != sbuckets)
+       free(old);
+    buckets = new;
+    hashlen = nlen;
+}
+
+static struct bucket *hashget(const struct source *src)
+{
+    unsigned int i, n, N, m;
+    struct bucket *bk;
+    
+    m = (N = (1 << hashlen)) - 1;
+    for(i = src->hash & m, n = 0; n < N; i = (i + 1) & m, n++) {
+       bk = buckets[i];
+       if(bk && !srccmp(&bk->id, src))
+           return(bk);
+    }
+    for(i = src->hash & m; buckets[i]; i = (i + 1) & m);
+    buckets[i] = bk = szmalloc(sizeof(*bk));
+    memcpy(&bk->id, src, sizeof(*src));
+    bk->last = bk->etime = now;
+    bk->thpos = -1;
+    bk->blocked = -1;
+    if(++nbuckets > (1 << (hashlen - 1)))
+       rehash(hashlen + 1);
+    return(bk);
+}
+
+static void hashdel(struct bucket *bk)
+{
+    unsigned int i, o, p, n, N, m;
+    struct bucket *sb;
+    
+    m = (N = (1 << hashlen)) - 1;
+    for(i = bk->id.hash & m, n = 0; n < N; i = (i + 1) & m, n++) {
+       assert((sb = buckets[i]) != NULL);
+       if(!srccmp(&sb->id, &bk->id))
+           break;
+    }
+    assert(sb == bk);
+    buckets[i] = NULL;
+    for(o = (i + 1) & m; buckets[o] != NULL; o = (o + 1) & m) {
+       sb = buckets[o];
+       p = (sb->id.hash - i) & m;
+       if((p == 0) || (p > ((o - i) & m))) {
+           buckets[i] = sb;
+           buckets[o] = NULL;
+           i = o;
+       }
+    }
+    if(--nbuckets <= (1 << (hashlen - 3)))
+       rehash(hashlen - 1);
+}
+
+static void thraise(struct btime bt, int n)
+{
+    int p;
+    
+    while(n > 0) {
+       p = (n - 1) >> 1;
+       if(timeheap.b[p].tm <= bt.tm)
+           break;
+       (timeheap.b[n] = timeheap.b[p]).bk->thpos = n;
+       n = p;
+    }
+    (timeheap.b[n] = bt).bk->thpos = n;
+}
+
+static void thlower(struct btime bt, int n)
+{
+    int c1, c2, c;
+    
+    while(1) {
+       c2 = (c1 = (n << 1) + 1) + 1;
+       if(c1 >= timeheap.d)
+           break;
+       c = ((c2 < timeheap.d) && (timeheap.b[c2].tm < timeheap.b[c1].tm)) ? c2 : c1;
+       if(timeheap.b[c].tm > bt.tm)
+           break;
+       (timeheap.b[n] = timeheap.b[c]).bk->thpos = n;
+       n = c;
+    }
+    (timeheap.b[n] = bt).bk->thpos = n;
+}
+
+static void thadjust(struct btime bt, int n)
+{
+    if((n > 0) && (timeheap.b[(n - 1) >> 1].tm > bt.tm))
+       thraise(bt, n);
+    else
+       thlower(bt, n);
+}
+
+static void freebucket(struct bucket *bk)
+{
+    int i, n;
+    struct btime r;
+    
+    hashdel(bk);
+    if((n = bk->thpos) >= 0) {
+       r = timeheap.b[--timeheap.d];
+       if(n < timeheap.d)
+           thadjust(r, n);
+    }
+    for(i = 0; i < bk->brim.d; i++) {
+       freehthead(bk->brim.b[i].req);
+       close(bk->brim.b[i].fd);
+    }
+    buffree(bk->brim);
+    free(bk);
+}
+
+static void updbtime(struct bucket *bk)
+{
+    double tm, tm2;
+    
+    tm = (bk->level == 0) ? (bk->etime + cf.retain) : (bk->last + (bk->level / cf.rate) + cf.retain);
+    if((bk->blocked > 0) && ((tm2 = bk->wtime + cf.warnrate) > tm))
+       tm = tm2;
+    
+    if((bk->brim.d > 0) && ((tm2 = bk->last + ((bk->level - cf.size) / cf.rate)) < tm))
+       tm = tm2;
+    if((bk->blocked > 0) && ((tm2 = bk->wtime + cf.warnrate) < tm))
+       tm = tm2;
+    
+    if(bk->thpos < 0) {
+       sizebuf(timeheap, ++timeheap.d);
+       thraise((struct btime){bk, tm}, timeheap.d - 1);
+    } else {
+       thadjust((struct btime){bk, tm}, bk->thpos);
+    }
+}
+
+static void tickbucket(struct bucket *bk)
+{
+    double delta, ll;
+    
+    delta = now - bk->last;
+    bk->last = now;
+    ll = bk->level;
+    if((bk->level -= delta * cf.rate) < 0) {
+       if(ll > 0)
+           bk->etime = now + (bk->level / cf.rate);
+       bk->level = 0;
+    }
+    while((bk->brim.d > 0) && (bk->level < cf.size)) {
+       if(sendreq(child, bk->brim.b[0].req, bk->brim.b[0].fd)) {
+           flog(LOG_ERR, "ratequeue: could not pass request to child: %s", strerror(errno));
+           exit(1);
+       }
+       freehthead(bk->brim.b[0].req);
+       close(bk->brim.b[0].fd);
+       bufdel(bk->brim, 0);
+       bk->level += 1;
+    }
+    if((bk->blocked > 0) && (now - bk->wtime >= cf.warnrate)) {
+       flog(LOG_NOTICE, "ratequeue: blocked %i requests from %s", bk->blocked, formatsrc(&bk->id));
+       bk->blocked = 0;
+       bk->wtime = now;
+    }
+}
+
+static void checkbtime(struct bucket *bk)
+{
+    tickbucket(bk);
+    if((bk->level == 0) && (now >= bk->etime + cf.retain) && (bk->blocked <= 0)) {
+       freebucket(bk);
+       return;
+    }
+    updbtime(bk);
+}
+
+static void serve(struct hthead *req, int fd)
+{
+    struct source src;
+    struct bucket *bk;
+    
+    now = rtime();
+    src = reqsource(req);
+    bk = hashget(&src);
+    tickbucket(bk);
+    if(bk->level < cf.size) {
+       bk->level += 1;
+       if(sendreq(child, req, fd)) {
+           flog(LOG_ERR, "ratequeue: could not pass request to child: %s", strerror(errno));
+           exit(1);
+       }
+       freehthead(req);
+       close(fd);
+    } else if(bk->brim.d < cf.brimsize) {
+       bufadd(bk->brim, ((struct waiting){.req = req, .fd = fd}));
+    } else {
+       if(bk->blocked < 0) {
+           flog(LOG_NOTICE, "ratequeue: blocking requests from %s", formatsrc(&bk->id));
+           bk->blocked = 0;
+           bk->wtime = now;
+       }
+       simpleerror(fd, 429, "Too many requests", "Your client is being throttled for issuing too frequent requests.");
+       freehthead(req);
+       close(fd);
+       bk->blocked++;
+    }
+    updbtime(bk);
+}
+
+static int parseint(const char *str, int *dst)
+{
+    long buf;
+    char *p;
+    
+    buf = strtol(str, &p, 0);
+    if((p == str) || *p)
+       return(-1);
+    *dst = buf;
+    return(0);
+}
+
+static int parsefloat(const char *str, double *dst)
+{
+    double buf;
+    char *p;
+    
+    buf = strtod(str, &p);
+    if((p == str) || *p)
+       return(-1);
+    *dst = buf;
+    return(0);
+}
+
+static int readconf(char *path, struct config *buf)
+{
+    FILE *fp;
+    struct cfstate *s;
+    int rv;
+    
+    if((fp = fopen(path, "r")) == NULL) {
+       flog(LOG_ERR, "ratequeue: %s: %s", path, strerror(errno));
+       return(-1);
+    }
+    *buf = defcfg;
+    s = mkcfparser(fp, path);
+    rv = -1;
+    while(1) {
+       getcfline(s);
+       if(!strcmp(s->argv[0], "eof")) {
+           break;
+       } else if(!strcmp(s->argv[0], "size")) {
+           if((s->argc < 2) || parsefloat(s->argv[1], &buf->size)) {
+               flog(LOG_ERR, "%s:%i: missing or invalid `size' argument");
+               goto err;
+           }
+       } else if(!strcmp(s->argv[0], "rate")) {
+           if((s->argc < 2) || parsefloat(s->argv[1], &buf->rate)) {
+               flog(LOG_ERR, "%s:%i: missing or invalid `rate' argument");
+               goto err;
+           }
+       } else if(!strcmp(s->argv[0], "brim")) {
+           if((s->argc < 2) || parseint(s->argv[1], &buf->brimsize)) {
+               flog(LOG_ERR, "%s:%i: missing or invalid `brim' argument");
+               goto err;
+           }
+       } else {
+           flog(LOG_WARNING, "%s:%i: unknown directive `%s'", s->file, s->lno, s->argv[0]);
+       }
+    }
+    rv = 0;
+err:
+    freecfparser(s);
+    fclose(fp);
+    return(rv);
+}
+
+static void huphandler(int sig)
+{
+    reload = 1;
+}
+
+static void usage(FILE *out)
+{
+    fprintf(out, "usage: ratequeue [-h] [-s BUCKET-SIZE] [-r RATE] [-b BRIM-SIZE] PROGRAM [ARGS...]\n");
+}
+
+int main(int argc, char **argv)
+{
+    int c, rv;
+    int fd;
+    struct hthead *req;
+    struct pollfd pfd;
+    double timeout;
+    char *cfname;
+    struct config cfbuf;
+    
+    cf = defcfg;
+    cfname = NULL;
+    while((c = getopt(argc, argv, "+hc:s:r:b:")) >= 0) {
+       switch(c) {
+       case 'h':
+           usage(stdout);
+           return(0);
+       case 'c':
+           cfname = optarg;
+           break;
+       case 's':
+           parsefloat(optarg, &cf.size);
+           break;
+       case 'r':
+           parsefloat(optarg, &cf.rate);
+           break;
+       case 'b':
+           parseint(optarg, &cf.brimsize);
+           break;
+       }
+    }
+    if(argc - optind < 1) {
+       usage(stderr);
+       return(1);
+    }
+    if(cfname) {
+       if(readconf(cfname, &cfbuf))
+           return(1);
+       cf = cfbuf;
+    }
+    if((child = stdmkchild(argv + optind, NULL, NULL)) < 0) {
+       flog(LOG_ERR, "ratequeue: could not fork child: %s", strerror(errno));
+       return(1);
+    }
+    sigaction(SIGHUP, &(struct sigaction){.sa_handler = huphandler}, NULL);
+    while(1) {
+       if(reload) {
+           if(cfname) {
+               if(!readconf(cfname, &cfbuf))
+                   cf = cfbuf;
+           }
+           reload = 0;
+       }
+       now = rtime();
+       pfd = (struct pollfd){.fd = 0, .events = POLLIN};
+       timeout = (timeheap.d > 0) ? timeheap.b[0].tm : -1;
+       if((rv = poll(&pfd, 1, (timeout < 0) ? -1 : (int)((timeout + 0.1 - now) * 1000))) < 0) {
+           if(errno != EINTR) {
+               flog(LOG_ERR, "ratequeue: error in poll: %s", strerror(errno));
+               exit(1);
+           }
+       }
+       if(pfd.revents) {
+           if((fd = recvreq(0, &req)) < 0) {
+               if(errno == EINTR)
+                   continue;
+               if(errno != 0)
+                   flog(LOG_ERR, "recvreq: %s", strerror(errno));
+               break;
+           }
+           serve(req, fd);
+       }
+       while((timeheap.d > 0) && ((now = rtime()) >= timeheap.b[0].tm))
+           checkbtime(timeheap.b[0].bk);
+    }
+    return(0);
+}
index 7aa1df0..5a11f94 100644 (file)
@@ -54,8 +54,7 @@ struct ncredbuf {
 };
 
 struct sslport {
-    int fd;
-    int sport;
+    int fd, sport, clreq;
     gnutls_certificate_credentials_t creds;
     gnutls_priority_t ciphers;
     struct namedcreds **ncreds;
@@ -74,6 +73,11 @@ struct savedsess {
     gnutls_datum_t key, value;
 };
 
+struct certbuffer {
+    gnutls_x509_crt_t *b;
+    size_t s, d;
+};
+
 static int numconn = 0, numsess = 0;
 static struct btree *sessidx = NULL;
 static struct savedsess *sesslistf = NULL, *sesslistl = NULL;
@@ -267,6 +271,8 @@ static int initreq(struct conn *conn, struct hthead *req)
     struct sslconn *ssl = conn->pdata;
     struct sockaddr_storage sa;
     socklen_t salen;
+    gnutls_datum_t sessid;
+    char *esessid;
     
     headappheader(req, "X-Ash-Address", formathaddress((struct sockaddr *)&ssl->name, sizeof(sa)));
     if(ssl->name.ss_family == AF_INET)
@@ -278,6 +284,43 @@ static int initreq(struct conn *conn, struct hthead *req)
        headappheader(req, "X-Ash-Server-Address", formathaddress((struct sockaddr *)&sa, sizeof(sa)));
     headappheader(req, "X-Ash-Server-Port", sprintf3("%i", ssl->port->sport));
     headappheader(req, "X-Ash-Protocol", "https");
+    if(gnutls_session_get_id2(ssl->sess, &sessid) == GNUTLS_E_SUCCESS) {
+       esessid = base64encode((void *)sessid.data, sessid.size);
+       headappheader(req, "X-Ash-TLS-Session", esessid);
+       free(esessid);
+    }
+    return(0);
+}
+
+static int setcreds(gnutls_session_t sess)
+{
+    int i, o, u;
+    struct sslport *pd;
+    unsigned int ntype;
+    char nambuf[256];
+    size_t namlen;
+    
+    pd = gnutls_session_get_ptr(sess);
+    for(i = 0; 1; i++) {
+       namlen = sizeof(nambuf);
+       if(gnutls_server_name_get(sess, nambuf, &namlen, &ntype, i) != 0)
+           break;
+       if(ntype != GNUTLS_NAME_DNS)
+           continue;
+       for(o = 0; pd->ncreds[o] != NULL; o++) {
+           for(u = 0; pd->ncreds[o]->names[u] != NULL; u++) {
+               if(!strcmp(pd->ncreds[o]->names[u], nambuf)) {
+                   gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->ncreds[o]->creds);
+                   if(pd->clreq)
+                       gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
+                   return(0);
+               }
+           }
+       }
+    }
+    gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->creds);
+    if(pd->clreq)
+       gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
     return(0);
 }
 
@@ -290,34 +333,6 @@ static void servessl(struct muth *muth, va_list args)
     struct sslconn ssl;
     gnutls_session_t sess;
     int ret;
-    
-    int setcreds(gnutls_session_t sess)
-    {
-       int i, o, u;
-       unsigned int ntype;
-       char nambuf[256];
-       size_t namlen;
-       
-       for(i = 0; 1; i++) {
-           namlen = sizeof(nambuf);
-           if(gnutls_server_name_get(sess, nambuf, &namlen, &ntype, i) != 0)
-               break;
-           if(ntype != GNUTLS_NAME_DNS)
-               continue;
-           for(o = 0; pd->ncreds[o] != NULL; o++) {
-               for(u = 0; pd->ncreds[o]->names[u] != NULL; u++) {
-                   if(!strcmp(pd->ncreds[o]->names[u], nambuf)) {
-                       gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->ncreds[o]->creds);
-                       gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
-                       return(0);
-                   }
-               }
-           }
-       }
-       gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->creds);
-       gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
-       return(0);
-    }
 
     numconn++;
     fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
@@ -327,6 +342,7 @@ static void servessl(struct muth *muth, va_list args)
     gnutls_db_set_store_function(sess, sessdbstore);
     gnutls_db_set_remove_function(sess, sessdbdel);
     gnutls_db_set_ptr(sess, NULL);
+    gnutls_session_set_ptr(sess, pd);
     gnutls_handshake_set_post_client_hello_function(sess, setcreds);
     gnutls_transport_set_ptr(sess, (gnutls_transport_ptr_t)(intptr_t)fd);
     while((ret = gnutls_handshake(sess)) != 0) {
@@ -421,20 +437,59 @@ static void init(void)
     }
 }
 
-static struct namedcreds *readncreds(char *file)
+/* This implementation seems somewhat ugly, but it's the way the
+ * GnuTLS implements the same thing internally, so it should probably
+ * be interoperable, at least. */
+static int readcrtchain(struct certbuffer *ret, struct charbuf *pem)
+{
+    static char *headers[] = {"-----BEGIN CERTIFICATE", "-----BEGIN X509 CERTIFICATE"};
+    int i, rv;
+    char *p, *p2, *f;
+    gnutls_x509_crt_t crt;
+    
+    for(i = 0, p = NULL; i < sizeof(headers) / sizeof(*headers); i++) {
+       f = memmem(pem->b, pem->d, headers[i], strlen(headers[i]));
+       if((f != NULL) && ((p == NULL) || (f < p)))
+           p = f;
+    }
+    if(p == NULL)
+       return(-GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
+    do {
+       if((rv = gnutls_x509_crt_init(&crt)) < 0)
+           goto error;
+       if((rv = gnutls_x509_crt_import(crt, &(gnutls_datum_t){.data = (unsigned char *)p, .size = pem->d - (p - pem->b)}, GNUTLS_X509_FMT_PEM)) < 0) {
+           gnutls_x509_crt_deinit(crt);
+           goto error;
+       }
+       bufadd(*ret, crt);
+       for(i = 0, p2 = NULL; i < sizeof(headers) / sizeof(*headers); i++) {
+           f = memmem(p + 1, pem->d - (p + 1 - pem->b), headers[i], strlen(headers[i]));
+           if((f != NULL) && ((p2 == NULL) || (f < p2)))
+               p2 = f;
+       }
+    } while((p = p2) != NULL);
+    return(0);
+error:
+    for(i = 0; i < ret->d; i++)
+       gnutls_x509_crt_deinit(ret->b[i]);
+    ret->d = 0;
+    return(rv);
+}
+
+static struct namedcreds *readncreds(char *file, gnutls_x509_privkey_t defkey)
 {
     int i, fd, ret;
     struct namedcreds *nc;
-    gnutls_x509_crt_t crt;
+    struct certbuffer crts;
     gnutls_x509_privkey_t key;
     char cn[1024];
     size_t cnl;
-    gnutls_datum_t d;
     struct charbuf keybuf;
     struct charvbuf names;
     unsigned int type;
     
     bufinit(keybuf);
+    bufinit(crts);
     bufinit(names);
     if((fd = open(file, O_RDONLY)) < 0) {
        flog(LOG_ERR, "ssl: %s: %s", file, strerror(errno));
@@ -452,15 +507,12 @@ static struct namedcreds *readncreds(char *file)
        keybuf.d += ret;
     }
     close(fd);
-    d.data = (unsigned char *)keybuf.b;
-    d.size = keybuf.d;
-    gnutls_x509_crt_init(&crt);
-    if((ret = gnutls_x509_crt_import(crt, &d, GNUTLS_X509_FMT_PEM)) != 0) {
-       flog(LOG_ERR, "ssl: could not load certificate from %s: %s", file, gnutls_strerror(ret));
+    if((ret = readcrtchain(&crts, &keybuf)) != 0) {
+       flog(LOG_ERR, "ssl: could not load certificate chain from %s: %s", file, gnutls_strerror(ret));
        exit(1);
     }
     cnl = sizeof(cn) - 1;
-    if((ret = gnutls_x509_crt_get_dn_by_oid(crt, GNUTLS_OID_X520_COMMON_NAME, 0, 0, cn, &cnl)) != 0) {
+    if((ret = gnutls_x509_crt_get_dn_by_oid(crts.b[0], GNUTLS_OID_X520_COMMON_NAME, 0, 0, cn, &cnl)) != 0) {
        flog(LOG_ERR, "ssl: could not read common name from %s: %s", file, gnutls_strerror(ret));
        exit(1);
     }
@@ -468,23 +520,28 @@ static struct namedcreds *readncreds(char *file)
     bufadd(names, sstrdup(cn));
     for(i = 0; 1; i++) {
        cnl = sizeof(cn) - 1;
-       if(gnutls_x509_crt_get_subject_alt_name2(crt, i, cn, &cnl, &type, NULL) < 0)
+       if(gnutls_x509_crt_get_subject_alt_name2(crts.b[0], i, cn, &cnl, &type, NULL) < 0)
            break;
        cn[cnl] = 0;
        if(type == GNUTLS_SAN_DNSNAME)
            bufadd(names, sstrdup(cn));
     }
     gnutls_x509_privkey_init(&key);
-    if((ret = gnutls_x509_privkey_import(key, &d, GNUTLS_X509_FMT_PEM)) != 0) {
-       flog(LOG_ERR, "ssl: could not load key from %s: %s", file, gnutls_strerror(ret));
-       exit(1);
+    if((ret = gnutls_x509_privkey_import(key, &(gnutls_datum_t){.data = (unsigned char *)keybuf.b, .size = keybuf.d}, GNUTLS_X509_FMT_PEM)) != 0) {
+       if(ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+           gnutls_x509_privkey_deinit(key);
+           key = defkey;
+       } else {
+           flog(LOG_ERR, "ssl: could not load key from %s: %s", file, gnutls_strerror(ret));
+           exit(1);
+       }
     }
     buffree(keybuf);
     bufadd(names, NULL);
     omalloc(nc);
     nc->names = names.b;
     gnutls_certificate_allocate_credentials(&nc->creds);
-    if((ret = gnutls_certificate_set_x509_key(nc->creds, &crt, 1, key)) != 0) {
+    if((ret = gnutls_certificate_set_x509_key(nc->creds, crts.b, crts.d, key)) != 0) {
        flog(LOG_ERR, "ssl: could not use certificate from %s: %s", file, gnutls_strerror(ret));
        exit(1);
     }
@@ -492,7 +549,7 @@ static struct namedcreds *readncreds(char *file)
     return(nc);
 }
 
-static void readncdir(struct ncredbuf *buf, char *dir)
+static void readncdir(struct ncredbuf *buf, char *dir, gnutls_x509_privkey_t defkey)
 {
     DIR *d;
     struct dirent *e;
@@ -509,23 +566,28 @@ static void readncdir(struct ncredbuf *buf, char *dir)
            continue;
        if(strcmp(e->d_name + es - 4, ".crt"))
            continue;
-       bufadd(*buf, readncreds(sprintf3("%s/%s", dir, e->d_name)));
+       bufadd(*buf, readncreds(sprintf3("%s/%s", dir, e->d_name), defkey));
     }
     closedir(d);
 }
 
 void handlegnussl(int argc, char **argp, char **argv)
 {
-    int i, ret, port, fd;
+    int i, ret, port, fd, clreq;
     gnutls_certificate_credentials_t creds;
     gnutls_priority_t ciphers;
+    gnutls_x509_privkey_t defkey;
     struct ncredbuf ncreds;
+    struct charvbuf ncertf, ncertd;
     struct sslport *pd;
     char *crtfile, *keyfile, *perr;
     
     init();
     port = 443;
+    clreq = 0;
     bufinit(ncreds);
+    bufinit(ncertf);
+    bufinit(ncertd);
     gnutls_certificate_allocate_credentials(&creds);
     keyfile = crtfile = NULL;
     ciphers = NULL;
@@ -589,6 +651,7 @@ void handlegnussl(int argc, char **argp, char **argv)
                    exit(1);
                }
            }
+           clreq = 1;
        } else if(!strcmp(argp[i], "crl")) {
            if((ret = gnutls_certificate_set_x509_crl_file(creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) {
                flog(LOG_ERR, "ssl: could not load CRL file `%s': %s", argv[i], gnutls_strerror(ret));
@@ -600,12 +663,13 @@ void handlegnussl(int argc, char **argp, char **argv)
                    exit(1);
                }
            }
+           clreq = 1;
        } else if(!strcmp(argp[i], "port")) {
            port = atoi(argv[i]);
        } else if(!strcmp(argp[i], "ncert")) {
-           bufadd(ncreds, readncreds(argv[i]));
+           bufadd(ncertf, argv[i]);
        } else if(!strcmp(argp[i], "ncertdir")) {
-           readncdir(&ncreds, argv[i]);
+           bufadd(ncertd, argv[i]);
        } else {
            flog(LOG_ERR, "unknown parameter `%s' to ssl handler", argp[i]);
            exit(1);
@@ -629,11 +693,22 @@ void handlegnussl(int argc, char **argp, char **argv)
        flog(LOG_ERR, "ssl: could not initialize cipher priorities: %s", gnutls_strerror(ret));
        exit(1);
     }
+    if((ret = gnutls_certificate_get_x509_key(creds, 0, &defkey)) != 0) {
+       flog(LOG_ERR, "ssl: could not get default key: %s", gnutls_strerror(ret));
+       exit(1);
+    }
+    for(i = 0; i < ncertf.d; i++)
+       bufadd(ncreds, readncreds(ncertf.b[i], defkey));
+    for(i = 0; i < ncertd.d; i++)
+       readncdir(&ncreds, ncertd.b[i], defkey);
+    buffree(ncertf);
+    buffree(ncertd);
     gnutls_certificate_set_dh_params(creds, dhparams());
     bufadd(ncreds, NULL);
     omalloc(pd);
     pd->fd = fd;
     pd->sport = port;
+    pd->clreq = clreq;
     pd->creds = creds;
     pd->ncreds = ncreds.b;
     pd->ciphers = ciphers;
diff --git a/src/ssl-openssl.c b/src/ssl-openssl.c
new file mode 100644 (file)
index 0000000..8fe7b9e
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+    ashd - A Sane HTTP Daemon
+    Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <utils.h>
+#include <mt.h>
+#include <mtio.h>
+#include <req.h>
+#include <log.h>
+#include <bufio.h>
+
+#include "htparser.h"
+
+#ifdef HAVE_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+struct sslport {
+    int fd, sport;
+    SSL_CTX *ctx;
+};
+
+struct sslconn {
+    struct sslport *port;
+    int fd;
+    SSL *ssl;
+    struct sockaddr *name;
+    socklen_t namelen;
+};
+
+static int tlsblock(int fd, int err, int to)
+{
+    if(err == SSL_ERROR_WANT_READ) {
+       if(block(fd, EV_READ, to) <= 0)
+           return(1);
+       return(0);
+    } else if(err == SSL_ERROR_WANT_WRITE) {
+       if(block(fd, EV_WRITE, to) <= 0)
+           return(1);
+       return(0);
+    } else {
+       return(1);
+    }
+}
+
+static ssize_t sslread(void *cookie, void *buf, size_t len)
+{
+    struct sslconn *sdat = cookie;
+    int ret, err, nb;
+    size_t off;
+    
+    off = 0;
+    while(off < len) {
+       nb = ((len - off) > INT_MAX) ? INT_MAX : (len - off);
+       if((ret = SSL_read(sdat->ssl, buf, nb)) <= 0) {
+           if(off > 0)
+               return(off);
+           err = SSL_get_error(sdat->ssl, ret);
+           if(err == SSL_ERROR_ZERO_RETURN) {
+               return(0);
+           } else if((err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE)) {
+               if(tlsblock(sdat->fd, err, 60)) {
+                   errno = ETIMEDOUT;
+                   return(-1);
+               }
+           } else {
+               if(err != SSL_ERROR_SYSCALL)
+                   errno = EPROTO;
+               return(-1);
+           }
+       } else {
+           off += ret;
+       }
+    }
+    return(off);
+}
+
+static ssize_t sslwrite(void *cookie, const void *buf, size_t len)
+{
+    struct sslconn *sdat = cookie;
+    int ret, err, nb;
+    size_t off;
+    
+    off = 0;
+    while(off < len) {
+       nb = ((len - off) > INT_MAX) ? INT_MAX : (len - off);
+       if((ret = SSL_write(sdat->ssl, buf, nb)) <= 0) {
+           if(off > 0)
+               return(off);
+           err = SSL_get_error(sdat->ssl, ret);
+           if((err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE)) {
+               if(tlsblock(sdat->fd, err, 60)) {
+                   errno = ETIMEDOUT;
+                   return(-1);
+               }
+           } else {
+               if(err != SSL_ERROR_SYSCALL)
+                   errno = EIO;
+               return(-1);
+           }
+       } else {
+           off += ret;
+       }
+    }
+    return(off);
+}
+
+static int sslclose(void *cookie)
+{
+    return(0);
+}
+
+static struct bufioops iofuns = {
+    .read = sslread,
+    .write = sslwrite,
+    .close = sslclose,
+};
+
+static int initreq(struct conn *conn, struct hthead *req)
+{
+    struct sslconn *sdat = conn->pdata;
+    struct sockaddr_storage sa;
+    socklen_t salen;
+    
+    headappheader(req, "X-Ash-Address", formathaddress(sdat->name, sdat->namelen));
+    if(sdat->name->sa_family == AF_INET)
+       headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)sdat->name)->sin_port)));
+    else if(sdat->name->sa_family == AF_INET6)
+       headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)sdat->name)->sin6_port)));
+    salen = sizeof(sa);
+    if(!getsockname(sdat->fd, (struct sockaddr *)&sa, &salen))
+       headappheader(req, "X-Ash-Server-Address", formathaddress((struct sockaddr *)&sa, salen));
+    headappheader(req, "X-Ash-Server-Port", sprintf3("%i", sdat->port->sport));
+    headappheader(req, "X-Ash-Protocol", "https");
+    return(0);
+}
+
+static void servessl(struct muth *muth, va_list args)
+{
+    vavar(int, fd);
+    vavar(struct sockaddr_storage, name);
+    vavar(struct sslport *, pd);
+    int ret;
+    SSL *ssl;
+    struct conn conn;
+    struct sslconn sdat;
+    
+    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+    ssl = SSL_new(pd->ctx);
+    SSL_set_fd(ssl, fd);
+    while((ret = SSL_accept(ssl)) <= 0) {
+       if(tlsblock(fd, SSL_get_error(ssl, ret), 60))
+           goto out;
+    }
+    memset(&conn, 0, sizeof(conn));
+    memset(&sdat, 0, sizeof(sdat));
+    conn.pdata = &sdat;
+    conn.initreq = initreq;
+    sdat.port = pd;
+    sdat.fd = fd;
+    sdat.ssl = ssl;
+    sdat.name = (struct sockaddr *)&name;
+    sdat.namelen = sizeof(name);
+    serve(bioopen(&sdat, &iofuns), fd, &conn);
+    while((ret = SSL_shutdown(ssl)) < 0) {
+       if(tlsblock(fd, SSL_get_error(ssl, ret), 60))
+           goto out;
+    }
+out:
+    SSL_free(ssl);
+    close(fd);
+}
+
+static void listenloop(struct muth *muth, va_list args)
+{
+    vavar(struct sslport *, pd);
+    int i, ns, n;
+    struct sockaddr_storage name;
+    socklen_t namelen;
+    
+    fcntl(pd->fd, F_SETFL, fcntl(pd->fd, F_GETFL) | O_NONBLOCK);
+    while(1) {
+       namelen = sizeof(name);
+       if(block(pd->fd, EV_READ, 0) == 0)
+           goto out;
+       for(n = 0; n < 100; n++) {
+           if((ns = accept(pd->fd, (struct sockaddr *)&name, &namelen)) < 0) {
+               if(errno == EAGAIN)
+                   break;
+               if(errno == ECONNABORTED)
+                   continue;
+               flog(LOG_ERR, "accept: %s", strerror(errno));
+               goto out;
+           }
+           mustart(servessl, ns, name, pd);
+       }
+    }
+    
+out:
+    close(pd->fd);
+    free(pd);
+    for(i = 0; i < listeners.d; i++) {
+       if(listeners.b[i] == muth)
+           bufdel(listeners, i);
+    }
+}
+
+void handleossl(int argc, char **argp, char **argv)
+{
+    int i, port, fd;
+    SSL_CTX *ctx;
+    char *crtfile, *keyfile;
+    struct sslport *pd;
+    
+    ctx = SSL_CTX_new(TLS_server_method());
+    if(!ctx) {
+       flog(LOG_ERR, "ssl: could not create context: %s", ERR_error_string(ERR_get_error(), NULL));
+       exit(1);
+    }
+    port = 443;
+    for(i = 0; i < argc; i++) {
+       if(!strcmp(argp[i], "help")) {
+           printf("ssl handler parameters:\n");
+           printf("\tcert=CERT-FILE  [mandatory]\n");
+           printf("\t\tThe name of the file to read the certificate from.\n");
+           printf("\tkey=KEY-FILE    [same as CERT-FILE]\n");
+           printf("\t\tThe name of the file to read the private key from.\n");
+           printf("\tport=PORT       [443]\n");
+           printf("\t\tThe TCP port to listen on.\n");
+           exit(0);
+       } else if(!strcmp(argp[i], "cert")) {
+           crtfile = argv[i];
+       } else if(!strcmp(argp[i], "key")) {
+           keyfile = argv[i];
+       } else if(!strcmp(argp[i], "port")) {
+           port = atoi(argv[i]);
+       } else {
+           flog(LOG_ERR, "unknown parameter `%s' to ssl handler", argp[i]);
+           exit(1);
+       }
+    }
+    if(crtfile == NULL) {
+       flog(LOG_ERR, "ssl: needs certificate file at the very least");
+       exit(1);
+    }
+    if(keyfile == NULL)
+       keyfile = crtfile;
+    if(SSL_CTX_use_certificate_file(ctx, crtfile, SSL_FILETYPE_PEM) <= 0) {
+       flog(LOG_ERR, "ssl: could not load certificate: %s", ERR_error_string(ERR_get_error(), NULL));
+       exit(1);
+    }
+    if(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) <= 0) {
+       flog(LOG_ERR, "ssl: could not load certificate: %s", ERR_error_string(ERR_get_error(), NULL));
+       exit(1);
+    }
+    if(!SSL_CTX_check_private_key(ctx)) {
+       flog(LOG_ERR, "ssl: key and certificate do not match");
+       exit(1);
+    }
+    if((fd = listensock6(port)) < 0) {
+       flog(LOG_ERR, "could not listen on IPv65 port (port %i): %s", port, strerror(errno));
+       exit(1);
+    }
+    omalloc(pd);
+    pd->fd = fd;
+    pd->sport = port;
+    pd->ctx = ctx;
+    bufadd(listeners, mustart(listenloop, pd));
+    if((fd = listensock4(port)) < 0) {
+       if(errno != EADDRINUSE) {
+           flog(LOG_ERR, "could not listen on IPv4 port (port %i): Is", port, strerror(errno));
+           exit(1);
+       }
+    } else {
+       omalloc(pd);
+       pd->fd = fd;
+       pd->sport = port;
+       pd->ctx = ctx;
+       bufadd(listeners, mustart(listenloop, pd));
+    }
+}
+
+#endif