From 4724a590413fc351d72211fd99df6c41decd0fa8 Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Wed, 20 Sep 2023 08:53:53 +0000 Subject: More doc, more fixes Signed-off-by: Laurent Bercot --- doc/index.html | 8 ++- doc/quickstart.html | 12 ++-- doc/tipideed.html | 177 ++++++++++++++++++++++++++++++++++++++++++++++-- src/tipideed/cgi.c | 36 ++++++---- src/tipideed/tipideed.c | 2 +- 5 files changed, 206 insertions(+), 29 deletions(-) diff --git a/doc/index.html b/doc/index.html index bbd868c..4a2b9b7 100644 --- a/doc/index.html +++ b/doc/index.html @@ -107,18 +107,20 @@ Only keeping the last three syllables makes it easier.
  • A POSIX-compliant system with a standard C development environment
  • GNU make, version 3.81 or later
  • skalibs version -2.13.2.0 or later. It's a build-time requirement. It's also a run-time +2.14.0.0 or later. It's a build-time requirement. It's also a run-time requirement if you link against the shared version of the skalibs library.
  • Recommended at run-time: s6-networking version -2.5.1.3 or later. It's not a strict requirement, but tipidee relies +2.5.1.4 or later. It's not a strict requirement, but tipidee relies on a super-server to listen to the network and provide connection information via environment variables. It also defers to tools such as s6-tcpserver-access to provide access control and connection fine-tuning. And if you want to run an HTTPS server, you'll need something like s6-tlsserver -to manage the TLS transport layer.
  • +to manage the TLS transport layer. So, installing +s6-networking will make +your life easier in many ways.

    Licensing

    diff --git a/doc/quickstart.html b/doc/quickstart.html index 8a393fb..a3e8519 100644 --- a/doc/quickstart.html +++ b/doc/quickstart.html @@ -52,9 +52,9 @@ If you want to serve HTTP on port 80 and HTTPS on port 443, then you'll need two services. Or four if you want to serve on both IPv4 and IPv6 adresses.
  • Start these processes in the /home/www directory, the base for all the domains you're serving.
  • -
  • Assuming you want to run the server as user www, -the basic command line for an HTTP service is: -s6-envuidgid www s6-tcpserver -U example.com 80 s6-tcpserver-access -v0 -- tipideed. +
  • Assuming you want to run the server as user www, and your +local IP address is ${ip}, the basic command line for an HTTP service is: +s6-envuidgid www s6-tcpserver -U -- ${ip} 80 s6-tcpserver-access -- tipideed.
    • s6-envuidgid puts the uid and gid of user www into the environment, for s6-tcpserver @@ -63,9 +63,9 @@ to drop root privileges to.
    • binds to the address and port given, drops privileges, and listens; it accepts connections and spawns a new process for each one.
    • s6-tcpserver-access -performs DNS requests to fill environment variables that tipidee needs. Its main -purpose is to perform access control, but we're not using it for that here: -chances are your web server is public access and doesn't need to be IP-restricted.
    • +performs DNS requests to fill environment variables that tipidee needs. (The main +purpose of this program is to perform access control, but we're not using it for that here: +chances are your web server is public access and doesn't need to be IP-restricted.)
    • tipideed is the tipidee daemon, and will handle HTTP requests until the client closes the connection or tipideed itself needs to close it.
    • diff --git a/doc/tipideed.html b/doc/tipideed.html index ce8f5e5..b6b39f6 100644 --- a/doc/tipideed.html +++ b/doc/tipideed.html @@ -42,8 +42,43 @@ occurs that makes it nonsensical to keep the connection open. current working directory, one subdirectory for every domain it hosts.
    +

    Common usage

    + +

    + tipideed is intended to be run under a TCP super-server such as +s6-tcpserver, +for plain text HTTP, or +s6-tlsserver, +for HTTPS. It delegates to the super-server the job of binding and listening to +the socket, accepting connections, spawning a separate process to handle a +given connection, and potentially establishing a TLS tunnel with the client for +secure communication. +

    +

    - TODO: write this page. + As such, a command line for tipideed, running as user www, listening +on address ${ip}, would typically look like this, for HTTP: +

    + +
    +     s6-envuidgid www s6-tcpserver -U -- ${ip} 80 s6-tcpserver-access -- tipideed
    +
    + +

    + or, for HTTPS: +

    + +
    +     s6-envuidgid www env KEYFILE=/path/to/private/key CERTFILE=/path/to/certificate s6-tlsserver -U -- ${ip} 443 tipideed
    +
    + +

    + Most users will want to run these command lines as services, i.e. daemons +run in the background when the machine starts. The examples/ subdirectory +of the tipidee package provides service templates to help you run tipideed under +OpenRC, +s6 and +s6-rc.

    Exit codes

    @@ -51,10 +86,16 @@ current working directory, one subdirectory for every domain it hosts.
  • 0
    clean exit. The client closed the connection after a stream of HTTP exchanges.
    +
    1
    Illicit client behaviour. tipideed exited because it could +not serve the client in good faith.
    +
    2
    Illicit CGI script behaviour. tipideed exited because the invoked +CGI script made it impossible to continue. Before exiting, tipideed likely has +sent a 502 (Bad Gateway) response to the client.
    100
    bad usage. tipideed has been run in an incorrect way: bad command line options, or missing environment variables, etc.
    101
    cannot happen. This signals a bug in tipideed, and comes with an -error message asking you to report the bug. Please do so.
    +error message asking you to report the bug. Please do so, on the +skaware mailing-list.
    111
    system call failed. If this happens while serving a request, tipideed likely has sent a 500 (Internal Server Error) response to the client before exiting.
    @@ -62,15 +103,143 @@ client before exiting.

    Environment variables

    +

    Reading - mandatory

    + +

    + tipideed expects the following variables in its environment, and will exit +with an error message if they are undefined. When tipideed is run under +s6-tcpserver +(with s6-tcpserver-access or +s6-tlsserver, +these variables are automatically set by the super-server. This is the way +tipidee gets its network information without having to perform network +operations itself. +

    + +
    +
    PROTO
    +
    The network protocol, normally TCP.
    + +
    TCPLOCALIP
    +
    The IP address the server is bound to. It will be passed as SERVER_ADDR +to CGI scripts.
    + +
    TCPLOCALPORT
    +
    The port the server is bound to. It will be passed as SERVER_PORT +to CGI scripts.
    + +
    TCPLOCALHOST
    +
    The domain name associated to the local IP address. It will be +passed as SERVER_NAME to CGI scripts.
    + +
    TCPREMOTEIP
    +
    The IP address of the client. It will be passed as REMOTE_ADDR +to CGI scripts.
    + +
    TCPREMOTEPORT
    +
    The port of the client socket. It will be passed as REMOTE_PORT +to CGI scripts.
    +
    + +

    Reading - optional

    + +

    + tipideed can function without these variables, but if they're present, it +uses them to get more information. +

    + +
    +
    TCPREMOTEHOST
    +
    The domain name associated to the IP address of the client. It will +be passed as REMOTE_HOST to CGI scripts; if absent, the value of +TCPREMOTEIP will be used instead.
    + +
    TCPREMOTEINFO
    +
    The name provided by an IDENT server running on the client, if any. +This is obsolete and not expected to be present; but if present, it will +be passed as REMOTE_IDENT to CGI scripts.
    + +
    SSL_PROTOCOL
    +
    The version of the TLS protocol used to cipher communications between +the client and the server. If present, tipideed will assume that the client +connection is secure, and will pass HTTPS=on to CGI scripts; +otherwise, it will assume it is running plaintext HTTP.
    +
    + +

    Writing

    + +

    + When spawning a CGI or NPH script, tipideed clears all the previous variables, +so the passed environment is as close as possible to the environment of the +super-server; and it adds all the variables that are required by the +CGI 1.1 +specification. It does not add PATH_TRANSLATED, which CGI scripts should +not rely on. +

    +

    Options

    +
    +
    -v verbosity
    +
    The level of log verbosity. This is the same as the global verbosity +setting in the configuration file; an explicit +command line option overrides any setting present in the configuration file.
    + +
    -f file
    +
    +
    +

    Detailed operation

    +

    Performance considerations

    + +

    + On systems that implement +posix_spawn(), +the s6-tcpserver +super-server (and the +s6-tlsserver one +as well, since both use the same underlying program) uses it instead of +fork(), +and that partly alleviates the performance penalty usually associated with servers +that spawn one process per connection. +

    + +

    + One of tipidee's stated goals is to explore what kind of performance is achievable for +a fully compliant Web server within the limits of that model. To that effect, tipideed +is meant to be fast. It should serve static files as fast as any server out +there, especially on Linux (or other systems supporting +splice()) where it +uses zero-copy transfer. CGI performance should be limited by the performance of the +CGI script itself, never by tipideed. +

    + +

    + tipideed itself does not use +fork() +if the system supports +posix_spawn() +— with one exception, that you will not hit, and if you do, fork() will not +be the bottleneck. (Can you guess which case it is, without looking at the code?) +tipideed does not parse its configuration file itself, delegating the task to the +offline tipidee-config program and directly mapping +a binary file instead. To parse a client request, it uses a deterministic finite +automaton, only reading the request once, and only backtracking in pathological cases. +This should streamline request processing as much as possible. +

    + +

    + If you have benchmarks, results of comparative testing of tipideed against +other Web servers, please share them on the +skaware mailing-list. +

    +

    Notes

      -
    • tipideed is pronounced tipi-deed. You can also say -tipi-dee-dee, but only if you're the type of person who says +
    • tipideed is pronounced tipi-deed. You can say +tipi-dee-dee, but only if you're the type of person who also says PC computer, NIC card or ATM machine.
    diff --git a/src/tipideed/cgi.c b/src/tipideed/cgi.c index 40300c7..1ce0b1d 100644 --- a/src/tipideed/cgi.c +++ b/src/tipideed/cgi.c @@ -111,10 +111,16 @@ static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char case -1 : die500sys(rql, 111, "fork") ; case 0 : { +#define NAME "tipideed (nph helper for pid " tain deadline ; char buf[4096] ; buffer b = BUFFER_INIT(&buffer_write, p[1], buf, 4096) ; - PROG = "tipidee (nph helper child)" ; + size_t m = sizeof(NAME) - 1 ; + char progstr[sizeof(NAME) + PID_FMT] ; + memcpy(progstr, NAME, m) ; + m += pid_fmt(progstr + m, getppid()) ; + progstr[m++] = ')' ; progstr[m++] = 0 ; + PROG = progstr ; tain_add_g(&deadline, &g.cgitto) ; close(p[0]) ; if (ndelay_on(p[1]) == -1) strerr_diefu1sys(111, "set fd nonblocking") ; @@ -205,8 +211,8 @@ static inline int run_cgi (tipidee_rql const *rql, char const *const *argv, char rstate = 1 ; break ; } - case 400 : die502x(rql, 1, "invalid headers", " from cgi ", argv[0]) ; - case 413 : die502x(rql, 1, hdr->n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data", " from cgi ", argv[0]) ; + case 400 : die502x(rql, 2, "invalid headers", " from cgi ", argv[0]) ; + case 413 : die502x(rql, 2, hdr->n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data", " from cgi ", argv[0]) ; case 500 : die500x(rql, 101, "can't happen: ", "avltreen_insert failed", " in do_cgi") ; default : die500x(rql, 101, "can't happen: ", "unknown tipidee_headers_parse return code", " in do_cgi") ; } @@ -217,7 +223,7 @@ static inline int run_cgi (tipidee_rql const *rql, char const *const *argv, char if (!slurpn(x[0].fd, sa, g.maxcgibody)) { if (error_isagain(errno)) break ; - else if (errno == ENOBUFS) die502x(rql, 1, "Too fat body", " from cgi ", argv[0]) ; + else if (errno == ENOBUFS) die502x(rql, 2, "Too fat body", " from cgi ", argv[0]) ; else die500sys(rql, 111, "read body", " from cgi ", argv[0]) ; } close(x[0].fd) ; @@ -243,7 +249,7 @@ static inline int local_redirect (tipidee_rql *rql, char const *loc, char *uribu memcpy(hosttmp, rql->uri.host, hostlen + 1) ; n = tipidee_uri_parse(uribuf, URI_BUFSIZE, loc, &rql->uri) ; if (!n || n + hostlen + 1 > URI_BUFSIZE) - die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Location", " value", " for local redirection") ; + die502x(rql, 2, "cgi ", cginame, " returned an invalid ", "Location", " value", " for local redirection") ; memcpy(uribuf + n, hosttmp, hostlen + 1) ; rql->uri.host = uribuf + n ; rql->uri.port = port ; @@ -297,23 +303,23 @@ static inline int process_cgi_output (tipidee_rql *rql, tipidee_headers const *h { size_t m = uint_scan(x, &status) ; if (!m || (x[m] && x[m] != ' ')) - die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " header") ; + die502x(rql, 2, "cgi ", cginame, " returned an invalid ", "Status", " header") ; reason_phrase = x[m] ? x + m + 1 : "" ; if (status >= 300 && status < 399 && !location) - die502x(rql, 1, "cgi ", cginame, " returned a 3xx status code without a ", "Location", " header") ; + die502x(rql, 2, "cgi ", cginame, " returned a 3xx status code without a ", "Location", " header") ; if (status < 100 || status > 999) - die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value") ; + die502x(rql, 2, "cgi ", cginame, " returned an invalid ", "Status", " value") ; } if (location) { - if (!location[0]) die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Location", " header") ; + if (!location[0]) die502x(rql, 2, "cgi ", cginame, " returned an invalid ", "Location", " header") ; if (location[0] == '/' && location[1] != '/') return local_redirect(rql, location, uribuf, cginame) ; if (rbodylen) { if (!status) - die502x(rql, 1, "cgi ", cginame, " didn't output a ", "Status", " header", " for a client redirect response with document") ; + die502x(rql, 2, "cgi ", cginame, " didn't output a ", "Status", " header", " for a client redirect response with document") ; if (status < 300 || status > 399) - die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value", " for a client redirect response with document") ; + die502x(rql, 2, "cgi ", cginame, " returned an invalid ", "Status", " value", " for a client redirect response with document") ; } else { @@ -322,7 +328,7 @@ static inline int process_cgi_output (tipidee_rql *rql, tipidee_headers const *h char const *key = hdr->buf + hdr->list[i].left ; if (!strcasecmp(key, "Location") || !strcasecmp(key, "Status")) continue ; if (str_start(key, "X-CGI-")) continue ; - die502x(rql, 1, "cgi ", cginame, " returned extra headers", " for a client redirect response without document") ; + die502x(rql, 2, "cgi ", cginame, " returned extra headers", " for a client redirect response without document") ; } if (!status) { @@ -335,16 +341,16 @@ static inline int process_cgi_output (tipidee_rql *rql, tipidee_headers const *h { if (!status) status = 200 ; if (!tipidee_headers_search(hdr, "Content-Type")) - die502x(rql, 1, "cgi ", cginame, " didn't output a ", "Content-Type", " header") ; + die502x(rql, 2, "cgi ", cginame, " didn't output a ", "Content-Type", " header") ; } x = tipidee_headers_search(hdr, "Content-Length") ; if (x) { size_t cln ; if (!size0_scan(x, &cln)) - die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Content-Length", " header") ; + die502x(rql, 2, "cgi ", cginame, " returned an invalid ", "Content-Length", " header") ; if (cln != rbodylen) - die502x(rql, 1, "cgi ", cginame, " returned a mismatching ", "Content-Length", " header") ; + die502x(rql, 2, "cgi ", cginame, " returned a mismatching ", "Content-Length", " header") ; } tipidee_response_status(buffer_1, rql, status, reason_phrase) ; diff --git a/src/tipideed/tipideed.c b/src/tipideed/tipideed.c index 0cc512c..8c1e16e 100644 --- a/src/tipideed/tipideed.c +++ b/src/tipideed/tipideed.c @@ -513,7 +513,7 @@ int main (int argc, char const *const *argv, char const *const *envp) while (serve(&rql, docroot, hostlen + 1 + g.localportlen, uribuf, &hdr, bodysa.s, bodysa.len)) if (localredirs++ >= MAX_LOCALREDIRS) - die502x(&rql, 1, "too many local redirections - possible loop involving path ", rql.uri.path) ; + die502x(&rql, 2, "too many local redirections - possible loop involving path ", rql.uri.path) ; } } log_and_exit(0) ; -- cgit v1.3.1