From 17c382d1c9d7236c101418060758d2296cc5e17e Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Sat, 5 Aug 2023 11:51:25 +0000 Subject: Initial commit Signed-off-by: Laurent Bercot --- .gitignore | 10 + AUTHORS | 2 + CONTRIBUTING | 5 + COPYING | 13 + DCO | 37 ++ INSTALL | 169 +++++++ Makefile | 149 ++++++ NEWS | 6 + README | 24 + configure | 485 +++++++++++++++++++ doc/index.html | 137 ++++++ doc/upgrade.html | 28 ++ etc/tipidee.conf | 61 +++ package/deps-build | 1 + package/deps.mak | 62 +++ package/info | 4 + package/modes | 3 + package/targets.mak | 8 + patch-for-solaris | 21 + src/config/PARSING-config.txt | 56 +++ src/config/PARSING-preprocess.txt | 38 ++ src/config/PROTOCOL.txt | 43 ++ src/config/confnode.c | 35 ++ src/config/conftree.c | 82 ++++ src/config/defaults.c | 106 +++++ src/config/deps-exe/tipidee-config | 6 + src/config/deps-exe/tipidee-config-preprocess | 1 + src/config/lexparse.c | 443 ++++++++++++++++++ src/config/tipidee-config-internal.h | 59 +++ src/config/tipidee-config-preprocess.c | 270 +++++++++++ src/config/tipidee-config.c | 135 ++++++ src/include/tipidee/body.h | 25 + src/include/tipidee/conf.h | 44 ++ src/include/tipidee/headers.h | 38 ++ src/include/tipidee/method.h | 32 ++ src/include/tipidee/response.h | 36 ++ src/include/tipidee/rql.h | 31 ++ src/include/tipidee/tipidee.h | 15 + src/include/tipidee/uri.h | 31 ++ src/libtipidee/deps-lib/tipidee | 24 + src/libtipidee/tipidee_chunked_read.c | 41 ++ src/libtipidee/tipidee_conf_free.c | 10 + src/libtipidee/tipidee_conf_get.c | 22 + src/libtipidee/tipidee_conf_get_argv.c | 32 ++ src/libtipidee/tipidee_conf_get_content_type.c | 22 + src/libtipidee/tipidee_conf_get_redirection.c | 35 ++ src/libtipidee/tipidee_conf_get_string.c | 17 + src/libtipidee/tipidee_conf_get_uint32.c | 19 + src/libtipidee/tipidee_conf_init.c | 10 + .../tipidee_headers_get_content_length.c | 17 + src/libtipidee/tipidee_headers_init.c | 29 ++ src/libtipidee/tipidee_headers_parse.c | 210 +++++++++ src/libtipidee/tipidee_headers_search.c | 13 + src/libtipidee/tipidee_method_conv_table.c | 19 + src/libtipidee/tipidee_method_tonum.c | 12 + src/libtipidee/tipidee_method_tostr.c | 8 + src/libtipidee/tipidee_response_error.c | 41 ++ src/libtipidee/tipidee_response_header_builtin.c | 40 ++ .../tipidee_response_header_common_put.c | 23 + src/libtipidee/tipidee_response_header_date_fmt.c | 24 + src/libtipidee/tipidee_response_status.c | 27 ++ src/libtipidee/tipidee_rql_read.c | 85 ++++ src/libtipidee/tipidee_uri_parse.c | 184 ++++++++ src/tipideed/cgi.c | 381 +++++++++++++++ src/tipideed/deps-exe/tipideed | 11 + src/tipideed/harden.c | 50 ++ src/tipideed/log.c | 59 +++ src/tipideed/options.c | 25 + src/tipideed/regular.c | 48 ++ src/tipideed/responses.c | 64 +++ src/tipideed/send_file.c | 123 +++++ src/tipideed/tipideed-internal.h | 147 ++++++ src/tipideed/tipideed.c | 514 +++++++++++++++++++++ src/tipideed/trace.c | 67 +++ tools/gen-deps.sh | 100 ++++ tools/install.sh | 69 +++ 76 files changed, 5373 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CONTRIBUTING create mode 100644 COPYING create mode 100644 DCO create mode 100644 INSTALL create mode 100644 Makefile create mode 100644 NEWS create mode 100644 README create mode 100755 configure create mode 100644 doc/index.html create mode 100644 doc/upgrade.html create mode 100644 etc/tipidee.conf create mode 100644 package/deps-build create mode 100644 package/deps.mak create mode 100644 package/info create mode 100644 package/modes create mode 100644 package/targets.mak create mode 100755 patch-for-solaris create mode 100644 src/config/PARSING-config.txt create mode 100644 src/config/PARSING-preprocess.txt create mode 100644 src/config/PROTOCOL.txt create mode 100644 src/config/confnode.c create mode 100644 src/config/conftree.c create mode 100644 src/config/defaults.c create mode 100644 src/config/deps-exe/tipidee-config create mode 100644 src/config/deps-exe/tipidee-config-preprocess create mode 100644 src/config/lexparse.c create mode 100644 src/config/tipidee-config-internal.h create mode 100644 src/config/tipidee-config-preprocess.c create mode 100644 src/config/tipidee-config.c create mode 100644 src/include/tipidee/body.h create mode 100644 src/include/tipidee/conf.h create mode 100644 src/include/tipidee/headers.h create mode 100644 src/include/tipidee/method.h create mode 100644 src/include/tipidee/response.h create mode 100644 src/include/tipidee/rql.h create mode 100644 src/include/tipidee/tipidee.h create mode 100644 src/include/tipidee/uri.h create mode 100644 src/libtipidee/deps-lib/tipidee create mode 100644 src/libtipidee/tipidee_chunked_read.c create mode 100644 src/libtipidee/tipidee_conf_free.c create mode 100644 src/libtipidee/tipidee_conf_get.c create mode 100644 src/libtipidee/tipidee_conf_get_argv.c create mode 100644 src/libtipidee/tipidee_conf_get_content_type.c create mode 100644 src/libtipidee/tipidee_conf_get_redirection.c create mode 100644 src/libtipidee/tipidee_conf_get_string.c create mode 100644 src/libtipidee/tipidee_conf_get_uint32.c create mode 100644 src/libtipidee/tipidee_conf_init.c create mode 100644 src/libtipidee/tipidee_headers_get_content_length.c create mode 100644 src/libtipidee/tipidee_headers_init.c create mode 100644 src/libtipidee/tipidee_headers_parse.c create mode 100644 src/libtipidee/tipidee_headers_search.c create mode 100644 src/libtipidee/tipidee_method_conv_table.c create mode 100644 src/libtipidee/tipidee_method_tonum.c create mode 100644 src/libtipidee/tipidee_method_tostr.c create mode 100644 src/libtipidee/tipidee_response_error.c create mode 100644 src/libtipidee/tipidee_response_header_builtin.c create mode 100644 src/libtipidee/tipidee_response_header_common_put.c create mode 100644 src/libtipidee/tipidee_response_header_date_fmt.c create mode 100644 src/libtipidee/tipidee_response_status.c create mode 100644 src/libtipidee/tipidee_rql_read.c create mode 100644 src/libtipidee/tipidee_uri_parse.c create mode 100644 src/tipideed/cgi.c create mode 100644 src/tipideed/deps-exe/tipideed create mode 100644 src/tipideed/harden.c create mode 100644 src/tipideed/log.c create mode 100644 src/tipideed/options.c create mode 100644 src/tipideed/regular.c create mode 100644 src/tipideed/responses.c create mode 100644 src/tipideed/send_file.c create mode 100644 src/tipideed/tipideed-internal.h create mode 100644 src/tipideed/tipideed.c create mode 100644 src/tipideed/trace.c create mode 100755 tools/gen-deps.sh create mode 100755 tools/install.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..649d51d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.o +*.a +*.lo +*.so +*.so.* +/config.mak +/src/include/tipidee/config.h +/tipideed +/tipidee-config +/tipidee-config-preprocess diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..7a708a1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Main author: + Laurent Bercot diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..6279422 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,5 @@ + Please add a Signed-Off-By: line at the end of your commit, +which certifies that you have the right and authority to pass +it on as an open-source patch, as explicited in the Developer's +Certificate of Origin available in this project's DCO file, +or at https://developercertificate.org/ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..fe81b1d --- /dev/null +++ b/COPYING @@ -0,0 +1,13 @@ +Copyright (c) 2023 Laurent Bercot + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/DCO b/DCO new file mode 100644 index 0000000..8201f99 --- /dev/null +++ b/DCO @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..67c4439 --- /dev/null +++ b/INSTALL @@ -0,0 +1,169 @@ +Build Instructions +------------------ + +* Requirements + ------------ + + - A POSIX-compliant C development environment + - GNU make version 3.81 or later + - skalibs version 2.13.2.0 or later: https://skarnet.org/software/skalibs/ + - (optional but recommended): s6-networking version 2.5.1.3 or later: + https://skarnet.org/software/s6-networking/ + + This software will run on any operating system that implements +POSIX.1-2008, available at: + https://pubs.opengroup.org/onlinepubs/9699919799/ + + +* Standard usage + -------------- + + ./configure && make && sudo make install + + will work for most users. + It will install the binaries in /usr/bin, and the shared and static +libraries in /usr/lib. + + You can strip the binaries and libraries of their extra symbols via +"make strip" before the "make install" phase. It will shave a few bytes +off them. + + +* Customization + ------------- + + You can customize paths via flags given to configure. + See ./configure --help for a list of all available configure options. + + +* Environment variables + --------------------- + + Controlling a build process via environment variables is a big and +dangerous hammer. You should try and pass flags to configure instead; +nevertheless, a few standard environment variables are recognized. + + If the CC environment variable is set, its value will override compiler +detection by configure. The --host=HOST option will still add a HOST- +prefix to the value of CC. + + The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags +auto-detected by configure. To entirely override the flags set by +configure instead, use make variables. + + +* Make variables + -------------- + + You can invoke make with a few variables for more configuration. + + CC, CFLAGS, CPPFLAGS, LDFLAGS, LDLIBS, AR, RANLIB, STRIP, INSTALL and +CROSS_COMPILE can all be overridden on the make command line. This is +an even bigger hammer than running ./configure with environment +variables, so it is advised to only do this when it is the only way of +obtaining the behaviour you want. + + DESTDIR can be given on the "make install" command line in order to +install to a staging directory. + + +* Shared libraries + ---------------- + + Software from skarnet.org is small enough that shared libraries are +generally not worth using. Static linking is simpler and incurs less +runtime overhead and less points of failure: so by default, shared +libraries are not built and binaries are linked against the static +versions of the skarnet.org libraries. Nevertheless, you can: + * build shared libraries: --enable-shared + * link binaries against shared libraries: --disable-allstatic + + +* Static binaries + --------------- + + By default, binaries are linked against static versions of all the +libraries they depend on, except for the libc. You can enforce +linking against the static libc with --enable-static-libc. + + Be aware that the GNU libc behaves badly with static linking and +produces huge executables, which is why it is not the default. +Other libcs are better suited to static linking, for instance +musl: https://musl-libc.org/ + + +* Cross-compilation + ----------------- + + skarnet.org packages centralize all the difficulty of +cross-compilation in one place: skalibs. Once you have +cross-compiled skalibs, the rest is easy. + + * Use the --host=HOST option to configure, HOST being the triplet +for your target. + * Make sure your cross-toolchain binaries (i.e. prefixed with HOST-) +are accessible via your PATH environment variable. + * Make sure to use the correct version of skalibs for your target, +and the correct sysdeps directory, making use of the +--with-include, --with-lib, --with-dynlib and --with-sysdeps +options as necessary. + + +* The slashpackage convention + --------------------------- + + The slashpackage convention (http://cr.yp.to/slashpackage.html) +is a package installation scheme that provides a few guarantees +over other conventions such as the FHS, for instance fixed +absolute pathnames. skarnet.org packages support it: use the +--enable-slashpackage option to configure, or +--enable-slashpackage=DIR for a prefixed DIR/package tree. +This option will activate slashpackage support during the build +and set slashpackage-compatible installation directories. +If $package_home is the home of the package, defined as +DIR/package/$category/$package-$version with the variables +read from the package/info file, then: + + --dynlibdir is set to $package_home/library.so + --bindir is set to $package_home/command + --sbindir is also set to $package_home/command (slashpackage +differentiates root-only binaries by their Unix rights, not their +location in the filesystem) + --libexecdir is also set to $package_home/command (slashpackage +does not need a specific directory for internal binaries) + --libdir is set to $package_home/library + --includedir is set to $package_home/include + + --prefix is pretty much ignored when you use --enable-slashpackage. +You should probably not use both --enable-slashpackage and --prefix. + + When using slashpackage, two additional Makefile targets are +available after "make install": + - "make update" changes the default version of the software to the +freshly installed one. (This is useful when you have several installed +versions of the same software, which slashpackage supports.) + - "make -L global-links" adds links from /command and /library.so to the +default version of the binaries and shared libraries. The "-L" option to +make is necessary because targets are symbolic links, and the default make +behaviour is to check the pointed file's timestamp and not the symlink's +timestamp. + + +* Absolute pathnames + ------------------ + + You may want to use fixed absolute pathnames even if you're not +following the slashpackage convention: for instance, the Nix packaging +system prefers calling binaries with immutable paths rather than rely on +PATH resolution. If you are in that case, use the --enable-absolute-paths +option to configure. This will ensure that programs calling binaries from +this package will call them with their full installation path (in bindir) +without relying on a PATH search. + + +* Out-of-tree builds + ------------------ + + skarnet.org packages do not support out-of-tree builds. They +are small, so it does not cost much to duplicate the entire +source tree if parallel builds are needed. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cd6c7f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,149 @@ +# +# This Makefile requires GNU make. +# +# Do not make changes here. +# Use the included .mak files. +# + +it: all + +make_need := 3.81 +ifeq "" "$(strip $(filter $(make_need), $(firstword $(sort $(make_need) $(MAKE_VERSION)))))" +fail := $(error Your make ($(MAKE_VERSION)) is too old. You need $(make_need) or newer) +endif + +CC = $(error Please use ./configure first) + +STATIC_LIBS := +SHARED_LIBS := +INTERNAL_LIBS := +EXTRA_TARGETS := +LIB_DEFS := + +define library_definition +LIB$(firstword $(subst =, ,$(1))) := lib$(lastword $(subst =, ,$(1))).$(if $(DO_ALLSTATIC),a,so).xyzzy +ifdef DO_SHARED +SHARED_LIBS += lib$(lastword $(subst =, ,$(1))).so.xyzzy +endif +ifdef DO_STATIC +STATIC_LIBS += lib$(lastword $(subst =, ,$(1))).a.xyzzy +endif +endef + +-include config.mak +include package/targets.mak + +$(foreach var,$(LIB_DEFS),$(eval $(call library_definition,$(var)))) + +include package/deps.mak + +version_m := $(basename $(version)) +version_M := $(basename $(version_m)) +version_l := $(basename $(version_M)) +CPPFLAGS_ALL := $(CPPFLAGS_AUTO) $(CPPFLAGS) +CFLAGS_ALL := $(CFLAGS_AUTO) $(CFLAGS) +ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),) +CFLAGS_SHARED := -fPIC +else +CFLAGS_SHARED := +endif +LDFLAGS_ALL := $(LDFLAGS_AUTO) $(LDFLAGS) +AR := $(CROSS_COMPILE)ar +RANLIB := $(CROSS_COMPILE)ranlib +STRIP := $(CROSS_COMPILE)strip +INSTALL := ./tools/install.sh + +ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS) +ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) $(INTERNAL_LIBS) +ALL_INCLUDES := $(wildcard src/include/$(package)/*.h) + +all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES) + +clean: + @exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo) $(EXTRA_TARGETS) + +distclean: clean + @exec rm -f config.mak src/include/$(package)/config.h + +tgz: distclean + @. package/info && \ + rm -rf /tmp/$$package-$$version && \ + cp -a . /tmp/$$package-$$version && \ + cd /tmp && \ + tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \ + exec rm -rf /tmp/$$package-$$version + +strip: $(ALL_LIBS) $(ALL_BINS) +ifneq ($(strip $(STATIC_LIBS)),) + exec $(STRIP) -x -R .note -R .comment $(STATIC_LIBS) +endif +ifneq ($(strip $(ALL_BINS)$(SHARED_LIBS)),) + exec $(STRIP) -R .note -R .comment $(ALL_BINS) $(SHARED_LIBS) +endif + +install: install-dynlib install-libexec install-bin install-lib install-include +install-dynlib: $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(dynlibdir)/lib%.so) +install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%) +install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%) +install-lib: $(STATIC_LIBS:lib%.a.xyzzy=$(DESTDIR)$(libdir)/lib%.a) +install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h) +install-data: $(ALL_DATA:src/etc/%=$(DESTDIR)$(datadir)/%) + +ifneq ($(exthome),) + +$(DESTDIR)$(exthome): $(DESTDIR)$(home) + exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome) + +update: $(DESTDIR)$(exthome) + +global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so.xyzzy=$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M)) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%) + +$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/% + exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/command/$( + + Please use the mailing-list for +questions about tipidee. + diff --git a/configure b/configure new file mode 100755 index 0000000..a835ee0 --- /dev/null +++ b/configure @@ -0,0 +1,485 @@ +#!/bin/sh + +cd `dirname "$0"` +. package/info + +usage () { +cat </dev/null 2>&1 && { echo "$1" ; return 0 ; } +$1 +EOF + echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g" +} + +fail () { + echo "$*" + exit 1 +} + +fnmatch () { + eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac" +} + +cmdexists () { + type "$1" >/dev/null 2>&1 +} + +trycc () { + test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO="$*" +} + +stripdir () { + while eval "fnmatch '*/' \"\${$1}\"" ; do + eval "$1=\${$1%/}" + done +} + +tryflag () { + echo "Checking whether compiler accepts $2 ..." + echo "typedef int x;" > "$tmpc" + if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST "$2" -c -o "$tmpo" "$tmpc" >/dev/null 2>&1 ; then + echo " ... yes" + eval "$1=\"\${$1} \$2\"" + eval "$1=\${$1# }" + return 0 + else + echo " ... no" + return 1 + fi +} + +tryldflag () { + echo "Checking whether linker accepts $2 ..." + echo "typedef int x;" > "$tmpc" + if $CC_AUTO $CFLAGS_AUTO $CFLAGS $CFLAGS_POST $LDFLAGS_AUTO $LDFLAGS $LDFLAGS_POST -nostdlib "$2" -o "$tmpe" "$tmpc" >/dev/null 2>&1 ; then + echo " ... yes" + eval "$1=\"\${$1} \$2\"" + eval "$1=\${$1# }" + return 0 + else + echo " ... no" + return 1 + fi +} + + +# Actual script + +CC_AUTO= +CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -iquote src/include-local -Isrc/include" +CPPFLAGS_POST="$CPPFLAGS" +CPPFLAGS= +CFLAGS_AUTO="-pipe -Wall" +CFLAGS_POST="$CFLAGS" +CFLAGS=-O2 +LDFLAGS_AUTO= +LDFLAGS_POST="$LDFLAGS" +LDFLAGS= +LDFLAGS_NOSHARED= +LDFLAGS_SHARED=-shared +prefix= +exec_prefix='$prefix' +dynlibdir='$prefix/lib' +libexecdir='$exec_prefix/libexec' +bindir='$exec_prefix/bin' +libdir='$prefix/lib/$package' +includedir='$prefix/include' +sysdeps='$prefix/lib/skalibs/sysdeps' +manualsysdeps=false +shared=false +static=true +allpic=true +slashpackage=false +abspath=false +usensss=false +sproot= +home= +exthome= +allstatic=true +evenmorestatic=false +addincpath='' +addlibspath='' +addlibdpath='' +vpaths='' +vpathd='' +build= + +for arg ; do + case "$arg" in + --help) usage ;; + --prefix=*) prefix=${arg#*=} ;; + --exec-prefix=*) exec_prefix=${arg#*=} ;; + --dynlibdir=*) dynlibdir=${arg#*=} ;; + --libexecdir=*) libexecdir=${arg#*=} ;; + --bindir=*) bindir=${arg#*=} ;; + --libdir=*) libdir=${arg#*=} ;; + --includedir=*) includedir=${arg#*=} ;; + --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;; + --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;; + --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;; + --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;; + --enable-shared|--enable-shared=yes) shared=true ;; + --disable-shared|--enable-shared=no) shared=false ;; + --enable-static|--enable-static=yes) static=true ;; + --disable-static|--enable-static=no) static=false ;; + --enable-allstatic|--enable-allstatic=yes) allstatic=true ;; + --disable-allstatic|--enable-allstatic=no) allstatic=false ; evenmorestatic=false ;; + --enable-static-libc|--enable-static-libc=yes) allstatic=true ; evenmorestatic=true ;; + --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;; + --enable-all-pic|--enable-all-pic=yes) allpic=true ;; + --disable-all-pic|--enable-all-pic=no) allpic=false ;; + --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;; + --enable-slashpackage) sproot= ; slashpackage=true ;; + --disable-slashpackage) sproot= ; slashpackage=false ;; + --enable-absolute-paths|--enable-absolute-paths=yes) abspath=true ;; + --disable-absolute-paths|--enable-absolute-paths=no) abspath=false ;; + --enable-nsss|--enable-nsss=yes) usensss=true ;; + --disable-nsss|--enable-nsss=no) usensss=false ;; + --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;; + --host=*|--target=*) target=${arg#*=} ;; + --build=*) build=${arg#*=} ;; + -* ) echo "$0: unknown option $arg" ;; + *=*) eval "${arg%%=*}=\${arg#*=}" ;; + *) target=$arg ;; + esac +done + +# Add /usr in the default default case +if test -z "$prefix" ; then + if test "$libdir" = '$prefix/lib/$package' ; then + libdir=/usr/lib/$package + fi + if test "$dynlibdir" = '$prefix/lib' ; then + dynlibdir=/usr/lib + fi + if test "$includedir" = '$prefix/include' ; then + includedir=/usr/include + fi + if test "$sysdeps" = '$prefix/lib/skalibs/sysdeps' ; then + sysdeps=/usr/lib/skalibs/sysdeps + fi +fi + +# Expand installation directories +stripdir prefix +for i in exec_prefix dynlibdir libexecdir bindir libdir includedir sysdeps sproot ; do + eval tmp=\${$i} + eval $i=$tmp + stripdir $i +done + +# Get usable temp filenames +i=0 +set -C +while : ; do + i=$(($i+1)) + tmpc="./tmp-configure-$$-$PPID-$i.c" + tmpo="./tmp-configure-$$-$PPID-$i.o" + tmpe="./tmp-configure-$$-$PPID-$i.tmp" + 2>|/dev/null > "$tmpc" && break + 2>|/dev/null > "$tmpo" && break + 2>|/dev/null > "$tmpe" && break + test "$i" -gt 50 && fail "$0: cannot create temporary files" +done +set +C +trap 'rm -f "$tmpc" "$tmpo" "$tmpe"' EXIT ABRT INT QUIT TERM HUP + +# Set slashpackage values +if $slashpackage ; then + home=${sproot}/package/${category}/${package}-${version} + exthome=${sproot}/package/${category}/${package} + if $manualsysdeps ; then + : + else + sysdeps=${DESTDIR}${sproot}/package/prog/skalibs/sysdeps + fi + extbinprefix=${exthome}/command + dynlibdir=${home}/library.so + bindir=${home}/command + libdir=${home}/library + libexecdir=$bindir + includedir=${home}/include + while read dep condvar ; do + if test -n "$condvar" ; then + eval "cond=$condvar" + else + cond=true + fi + if $cond ; then + addincpath="$addincpath -I${DESTDIR}${sproot}${dep}/include" + vpaths="$vpaths ${DESTDIR}${sproot}${dep}/library" + addlibspath="$addlibspath -L${DESTDIR}${sproot}${dep}/library" + vpathd="$vpathd ${DESTDIR}${sproot}${dep}/library.so" + addlibdpath="$addlibdpath -L${DESTDIR}${sproot}${dep}/library.so" + fi + done < package/deps-build +fi + +# Find a C compiler to use +if test -n "$target" && test x${build} != x${target} ; then + cross=${target}- +else + cross= +fi +echo "Checking for C compiler..." +trycc ${CC} +if test -n "$CC_AUTO" ; then + b=`basename "$CC"` + adjust_cross=false + if test "$b" != "$CC" ; then + adjust_cross=true + echo "$0: warning: compiler $CC is declared with its own path. If it's not accessible via PATH, you will need to pass AR, RANLIB and STRIP make variables to the make invocation." 1>&2 + fi + if test -n "$cross" ; then + if test "$b" = "${b##$cross}" ; then + echo "$0: warning: compiler $CC is declared as a cross-compiler for target $target but does not start with prefix ${cross}" 1>&2 + elif $adjust_cross ; then + cross=`dirname "$CC"`/"$cross" + fi + fi +fi +trycc ${cross}gcc +trycc ${cross}clang +trycc ${cross}cc +test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; } +echo " ... $CC_AUTO" +echo "Checking whether C compiler works... " +echo "typedef int x;" > "$tmpc" +if $CC_AUTO $CPPFLAGS_AUTO $CPPFLAGS $CPPFLAGS_POST $CFLAGS_AUTO $CFLAGS $CFLAGS_POST -c -o "$tmpo" "$tmpc" 2>"$tmpe" ; then + echo " ... yes" +else + echo " ... no. Compiler output follows:" + cat < "$tmpe" + exit 1 +fi + +echo "Checking target system type..." +if test -z "$target" ; then + if test -n "$build" ; then + target=$build ; + else + target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown + fi +fi +echo " ... $target" +if test ! -d $sysdeps || test ! -f $sysdeps/target ; then + echo "$0: error: $sysdeps is not a valid sysdeps directory" + exit 1 +fi +if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then + echo "$0: error: target $target does not match the contents of $sysdeps/target" + exit 1 +fi + +spawn_lib=$(cat $sysdeps/spawn.lib) +socket_lib=$(cat $sysdeps/socket.lib) +sysclock_lib=$(cat $sysdeps/sysclock.lib) +timer_lib=$(cat $sysdeps/timer.lib) +util_lib=$(cat $sysdeps/util.lib) + +if $allpic ; then + tryflag CPPFLAGS_AUTO -fPIC +fi +tryflag CFLAGS_AUTO -std=c99 +tryflag CFLAGS -fomit-frame-pointer +tryflag CFLAGS_AUTO -fno-exceptions +tryflag CFLAGS_AUTO -fno-unwind-tables +tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables +tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration +tryflag CPPFLAGS_AUTO -Werror=implicit-int +tryflag CPPFLAGS_AUTO -Werror=pointer-sign +tryflag CPPFLAGS_AUTO -Werror=pointer-arith +tryflag CFLAGS_AUTO -ffunction-sections +tryflag CFLAGS_AUTO -fdata-sections + +tryldflag LDFLAGS_AUTO -Wl,--sort-section=alignment +tryldflag LDFLAGS_AUTO -Wl,--sort-common + +CPPFLAGS_AUTO="${CPPFLAGS_AUTO}${addincpath}" + +if $evenmorestatic ; then + LDFLAGS_NOSHARED=-static +fi + +if $shared ; then + tryldflag LDFLAGS -Wl,--hash-style=both +fi + +LDFLAGS_SHARED="${LDFLAGS_SHARED}${addlibdpath}" + +if test -z "$vpaths" ; then + while read dep ; do + base=$(basename $dep) ; + vpaths="$vpaths /usr/lib/$base" + addlibspath="$addlibspath -L/usr/lib/$base" + done < package/deps-build +fi + +if $allstatic ; then + LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibspath}" + tryldflag LDFLAGS_NOSHARED -Wl,--gc-sections +else + LDFLAGS_NOSHARED="${LDFLAGS_NOSHARED}${addlibdpath}" +fi + +echo "Creating config.mak..." +cmdline=$(quote "$0") +for i ; do cmdline="$cmdline $(quote "$i")" ; done +exec 3>&1 1>config.mak +cat << EOF +# This file was generated by: +# $cmdline +# Any changes made here will be lost if configure is re-run. + +target := $target +package := $package +prefix := $prefix +exec_prefix := $exec_prefix +dynlibdir := $dynlibdir +libexecdir := $libexecdir +bindir := $bindir +libdir := $libdir +includedir := $includedir +sysdeps := $sysdeps +slashpackage := $slashpackage +sproot := $sproot +version := $version +home := $home +exthome := $exthome +SPAWN_LIB := ${spawn_lib} +SOCKET_LIB := ${socket_lib} +SYSCLOCK_LIB := ${sysclock_lib} +TIMER_LIB := ${timer_lib} +UTIL_LIB := ${util_lib} + +CC := $CC_AUTO +CPPFLAGS_AUTO := $CPPFLAGS_AUTO +CPPFLAGS := $CPPFLAGS $CPPFLAGS_POST +CFLAGS_AUTO := $CFLAGS_AUTO +CFLAGS := $CFLAGS $CFLAGS_POST +LDFLAGS_AUTO := $LDFLAGS_AUTO +LDFLAGS := $LDFLAGS $LDFLAGS_POST +LDFLAGS_SHARED := $LDFLAGS_SHARED +LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED +CROSS_COMPILE := $cross + +vpath lib%.a$vpaths +vpath lib%.so$vpathd +EOF +if $allstatic ; then + echo ".LIBPATTERNS := lib%.a" + echo "DO_ALLSTATIC := 1" +else + echo ".LIBPATTERNS := lib%.so" +fi +if $static ; then + echo "DO_STATIC := 1" +else + echo "DO_STATIC :=" +fi +if $shared ; then + echo "DO_SHARED := 1" +else + echo "DO_SHARED :=" +fi +if $allpic ; then + echo "STATIC_LIBS_ARE_PIC := 1" +else + echo "STATIC_LIBS_ARE_PIC :=" +fi +if $usensss ; then + echo "LIBNSSS := -lnsss" + echo "MAYBEPTHREAD_LIB := -lpthread" +else + echo "LIBNSSS :=" + echo "MAYBEPTHREAD_LIB :=" +fi + +exec 1>&3 3>&- +echo " ... done." + +echo "Creating src/include/${package}/config.h..." +mkdir -p -m 0755 src/include/${package} +exec 3>&1 1> src/include/${package}/config.h +cat <&3 3>&- +echo " ... done." diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..9f3bc2f --- /dev/null +++ b/doc/index.html @@ -0,0 +1,137 @@ + + + + + + tipidee - a small and fast HTTP/1.1 server + + + + + + +

+Software
+skarnet.org +

+ +

tipidee

+ +

What is it ?

+ +

+ tipidee is a web server. It supports HTTP/1.1. It aims to be compliant +with RFC 9112: +while it only implements a very limited subset of the optional functionality +in HTTP/1.1, it implements all the mandatory parts. +

+ +
+ +

Why another Web server?

+ +

+ There are two groups of web servers. +

+ +

+ The first one is big, powerful servers such as +nginx, +Apache httpd, +and so on. They focus on environments where simplicity isn't a concern +and can be traded off for features, integration with larger ecosystems, +or just serving speed. (lighttpd, +for instance, was developed as a proof of concept for solving the +c10k problem.) +

+ +

+ The second one is +

+ +

And why "tipidee"?

+ +

+ Because h-t-t-p-d is pretty tedious to say out loud, and only +keeping the last syllables makes it easier. +

+ +

Installation

+ +

Requirements

+ +
    +
  • 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 +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 +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.
  • +
+ +

Licensing

+ +

+ tipidee is free software. It is available under the +ISC license. +

+ +

Download

+ +
    +
  • The current released version of tipidee is +0.0.1.0.
  • +
  • You can checkout a copy of the +tipidee +git repository: +
     git clone git://git.skarnet.org/tipidee 
  • +
  • There's also a +GitHub mirror +of the tipidee git repository.
  • +
+ +

Compilation

+ +
    +
  • See the enclosed INSTALL file for installation details.
  • +
+ +

Upgrade notes

+ +
    +
  • This page lists the differences to be aware of between +the previous versions of tipidee and the current one.
  • +
+ +
+ +

Reference

+ +

Commands

+ + + +

Internal commands

+ + + +

Related resources

+ +
    +
  • tipidee is discussed on the +skaware mailing-list.
  • +
+ + + diff --git a/doc/upgrade.html b/doc/upgrade.html new file mode 100644 index 0000000..c6c9b70 --- /dev/null +++ b/doc/upgrade.html @@ -0,0 +1,28 @@ + + + + + + tipidee: how to upgrade + + + + + + +

+tipidee
+Software
+skarnet.org +

+ +

What has changed in tipidee

+ +

in 0.0.1.0

+ +
    +
  • Initial release.
  • +
+ + + diff --git a/etc/tipidee.conf b/etc/tipidee.conf new file mode 100644 index 0000000..cbbfac6 --- /dev/null +++ b/etc/tipidee.conf @@ -0,0 +1,61 @@ +# tipideed verbosity (overridden by the -v option). +# 0 is quiet, 1 is normal, 2+ is verbose. +# global verbosity 1 + +# tipideed will exit if the client does not send a new request +# after N milliseconds. +# 0 means no timeout. +# global read_timeout 0 + +# tipideed will drop the connection if the client +# does not read answers for N milliseconds. +# 0 means no timeout. +# global write_timeout 0 + +# tipideed will answer 504 ("Gateway Timeout") if a CGI program +# (including NPH) does not complete in N milliseconds. +# 0 means no timeout. +# global cgi_timeout 0 + +# tipideed will refuse to serve POST requests if the client- +# provided data is larger than N bytes. +# global max_request_body_length 8192 + +# tipideed will answer 502 ("Bad Gateway") if a CGI script's answer +# is larger than N bytes. (does not apply to NPH scripts) +# global max_cgi_body_length 4194304 + +# When the requested URL is a directory, tipideed will serve the first +# existing file in this list. (Useful e.g. if you have an index.cgi program.) +# global index_file index.html + +# You can define your own Content-Type mappings by file extension. +# The default mappings should work well for most servers. +# content-type text/html .html .htm + + +# The domain to which the next directives apply. +# You need one domain directive for each of the domains you serve, +# domain example.com + +# If a CGI script under the current domain starts with this prefix, +# it will be considered a NPH script (non-parsable headers). +# nph-prefix nph- + +# If uncommented: every file under that directory will be considered +# a CGI script. +# cgi /cgi-bin/ + +# You can also declare that individual files are CGI scripts. +# cgi /index.cgi + +# If uncommented: every CGI script under that directory will be considered NPH. +# nph /cgi-bin/nph/ + +# You can also declare that individual scripts are NPH. +# nph /cgi-bin/basic.cgi + +# You can override the default Content-Type for individual files, +# no matter what extension they have. +# file-type /source/document.html text/plain + diff --git a/package/deps-build b/package/deps-build new file mode 100644 index 0000000..05d5af4 --- /dev/null +++ b/package/deps-build @@ -0,0 +1 @@ +/package/prog/skalibs diff --git a/package/deps.mak b/package/deps.mak new file mode 100644 index 0000000..0dc6776 --- /dev/null +++ b/package/deps.mak @@ -0,0 +1,62 @@ +# +# This file has been generated by tools/gen-deps.sh +# + +src/include/tipidee/conf.h: src/include/tipidee/uri.h +src/include/tipidee/response.h: src/include/tipidee/rql.h +src/include/tipidee/rql.h: src/include/tipidee/method.h src/include/tipidee/uri.h +src/include/tipidee/tipidee.h: src/include/tipidee/body.h src/include/tipidee/conf.h src/include/tipidee/config.h src/include/tipidee/headers.h src/include/tipidee/method.h src/include/tipidee/response.h src/include/tipidee/rql.h src/include/tipidee/uri.h +src/tipideed/tipideed-internal.h: src/include/tipidee/tipidee.h +src/config/confnode.o src/config/confnode.lo: src/config/confnode.c src/config/tipidee-config-internal.h +src/config/conftree.o src/config/conftree.lo: src/config/conftree.c src/config/tipidee-config-internal.h +src/config/defaults.o src/config/defaults.lo: src/config/defaults.c src/config/tipidee-config-internal.h +src/config/lexparse.o src/config/lexparse.lo: src/config/lexparse.c src/config/tipidee-config-internal.h src/include/tipidee/config.h +src/config/tipidee-config-preprocess.o src/config/tipidee-config-preprocess.lo: src/config/tipidee-config-preprocess.c +src/config/tipidee-config.o src/config/tipidee-config.lo: src/config/tipidee-config.c src/config/tipidee-config-internal.h src/include/tipidee/config.h +src/libtipidee/tipidee_chunked_read.o src/libtipidee/tipidee_chunked_read.lo: src/libtipidee/tipidee_chunked_read.c src/include/tipidee/body.h +src/libtipidee/tipidee_conf_free.o src/libtipidee/tipidee_conf_free.lo: src/libtipidee/tipidee_conf_free.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_get.o src/libtipidee/tipidee_conf_get.lo: src/libtipidee/tipidee_conf_get.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_get_argv.o src/libtipidee/tipidee_conf_get_argv.lo: src/libtipidee/tipidee_conf_get_argv.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_get_content_type.o src/libtipidee/tipidee_conf_get_content_type.lo: src/libtipidee/tipidee_conf_get_content_type.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_get_redirection.o src/libtipidee/tipidee_conf_get_redirection.lo: src/libtipidee/tipidee_conf_get_redirection.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_get_string.o src/libtipidee/tipidee_conf_get_string.lo: src/libtipidee/tipidee_conf_get_string.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_get_uint32.o src/libtipidee/tipidee_conf_get_uint32.lo: src/libtipidee/tipidee_conf_get_uint32.c src/include/tipidee/conf.h +src/libtipidee/tipidee_conf_init.o src/libtipidee/tipidee_conf_init.lo: src/libtipidee/tipidee_conf_init.c src/include/tipidee/conf.h +src/libtipidee/tipidee_headers_get_content_length.o src/libtipidee/tipidee_headers_get_content_length.lo: src/libtipidee/tipidee_headers_get_content_length.c src/include/tipidee/headers.h +src/libtipidee/tipidee_headers_init.o src/libtipidee/tipidee_headers_init.lo: src/libtipidee/tipidee_headers_init.c src/include/tipidee/headers.h +src/libtipidee/tipidee_headers_parse.o src/libtipidee/tipidee_headers_parse.lo: src/libtipidee/tipidee_headers_parse.c src/include/tipidee/headers.h +src/libtipidee/tipidee_headers_search.o src/libtipidee/tipidee_headers_search.lo: src/libtipidee/tipidee_headers_search.c src/include/tipidee/headers.h +src/libtipidee/tipidee_method_conv_table.o src/libtipidee/tipidee_method_conv_table.lo: src/libtipidee/tipidee_method_conv_table.c src/include/tipidee/method.h +src/libtipidee/tipidee_method_tonum.o src/libtipidee/tipidee_method_tonum.lo: src/libtipidee/tipidee_method_tonum.c src/include/tipidee/method.h +src/libtipidee/tipidee_method_tostr.o src/libtipidee/tipidee_method_tostr.lo: src/libtipidee/tipidee_method_tostr.c src/include/tipidee/method.h +src/libtipidee/tipidee_response_error.o src/libtipidee/tipidee_response_error.lo: src/libtipidee/tipidee_response_error.c src/include/tipidee/method.h src/include/tipidee/response.h src/include/tipidee/rql.h +src/libtipidee/tipidee_response_header_builtin.o src/libtipidee/tipidee_response_header_builtin.lo: src/libtipidee/tipidee_response_header_builtin.c src/include/tipidee/config.h src/include/tipidee/response.h +src/libtipidee/tipidee_response_header_common_put.o src/libtipidee/tipidee_response_header_common_put.lo: src/libtipidee/tipidee_response_header_common_put.c src/include/tipidee/config.h src/include/tipidee/response.h +src/libtipidee/tipidee_response_header_date_fmt.o src/libtipidee/tipidee_response_header_date_fmt.lo: src/libtipidee/tipidee_response_header_date_fmt.c src/include/tipidee/response.h +src/libtipidee/tipidee_response_status.o src/libtipidee/tipidee_response_status.lo: src/libtipidee/tipidee_response_status.c src/include/tipidee/response.h +src/libtipidee/tipidee_rql_read.o src/libtipidee/tipidee_rql_read.lo: src/libtipidee/tipidee_rql_read.c src/include/tipidee/method.h src/include/tipidee/rql.h src/include/tipidee/uri.h +src/libtipidee/tipidee_uri_parse.o src/libtipidee/tipidee_uri_parse.lo: src/libtipidee/tipidee_uri_parse.c src/include/tipidee/uri.h +src/tipideed/cgi.o src/tipideed/cgi.lo: src/tipideed/cgi.c src/include/tipidee/headers.h src/include/tipidee/method.h src/include/tipidee/response.h src/include/tipidee/uri.h src/tipideed/tipideed-internal.h +src/tipideed/harden.o src/tipideed/harden.lo: src/tipideed/harden.c src/tipideed/tipideed-internal.h +src/tipideed/log.o src/tipideed/log.lo: src/tipideed/log.c src/include/tipidee/method.h src/tipideed/tipideed-internal.h +src/tipideed/options.o src/tipideed/options.lo: src/tipideed/options.c src/include/tipidee/response.h src/tipideed/tipideed-internal.h +src/tipideed/regular.o src/tipideed/regular.lo: src/tipideed/regular.c src/include/tipidee/method.h src/include/tipidee/response.h src/tipideed/tipideed-internal.h +src/tipideed/responses.o src/tipideed/responses.lo: src/tipideed/responses.c src/include/tipidee/response.h src/include/tipidee/rql.h src/tipideed/tipideed-internal.h +src/tipideed/send_file.o src/tipideed/send_file.lo: src/tipideed/send_file.c src/tipideed/tipideed-internal.h +src/tipideed/tipideed.o src/tipideed/tipideed.lo: src/tipideed/tipideed.c src/include/tipidee/tipidee.h src/tipideed/tipideed-internal.h +src/tipideed/trace.o src/tipideed/trace.lo: src/tipideed/trace.c src/include/tipidee/method.h src/include/tipidee/response.h src/tipideed/tipideed-internal.h + +tipidee-config: EXTRA_LIBS := -lskarnet ${SPAWN_LIB} +tipidee-config: src/config/tipidee-config.o src/config/confnode.o src/config/conftree.o src/config/defaults.o src/config/lexparse.o +tipidee-config-preprocess: EXTRA_LIBS := -lskarnet +tipidee-config-preprocess: src/config/tipidee-config-preprocess.o +ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),) +libtipidee.a.xyzzy: src/libtipidee/tipidee_chunked_read.o src/libtipidee/tipidee_conf_free.o src/libtipidee/tipidee_conf_get.o src/libtipidee/tipidee_conf_get_argv.o src/libtipidee/tipidee_conf_get_content_type.o src/libtipidee/tipidee_conf_get_redirection.o src/libtipidee/tipidee_conf_get_string.o src/libtipidee/tipidee_conf_get_uint32.o src/libtipidee/tipidee_conf_init.o src/libtipidee/tipidee_headers_get_content_length.o src/libtipidee/tipidee_headers_init.o src/libtipidee/tipidee_headers_parse.o src/libtipidee/tipidee_headers_search.o src/libtipidee/tipidee_method_conv_table.o src/libtipidee/tipidee_method_tonum.o src/libtipidee/tipidee_method_tostr.o src/libtipidee/tipidee_response_error.o src/libtipidee/tipidee_response_header_builtin.o src/libtipidee/tipidee_response_header_common_put.o src/libtipidee/tipidee_response_header_date_fmt.o src/libtipidee/tipidee_response_status.o src/libtipidee/tipidee_rql_read.o src/libtipidee/tipidee_uri_parse.o +else +libtipidee.a.xyzzy: src/libtipidee/tipidee_chunked_read.lo src/libtipidee/tipidee_conf_free.lo src/libtipidee/tipidee_conf_get.lo src/libtipidee/tipidee_conf_get_argv.lo src/libtipidee/tipidee_conf_get_content_type.lo src/libtipidee/tipidee_conf_get_redirection.lo src/libtipidee/tipidee_conf_get_string.lo src/libtipidee/tipidee_conf_get_uint32.lo src/libtipidee/tipidee_conf_init.lo src/libtipidee/tipidee_headers_get_content_length.lo src/libtipidee/tipidee_headers_init.lo src/libtipidee/tipidee_headers_parse.lo src/libtipidee/tipidee_headers_search.lo src/libtipidee/tipidee_method_conv_table.lo src/libtipidee/tipidee_method_tonum.lo src/libtipidee/tipidee_method_tostr.lo src/libtipidee/tipidee_response_error.lo src/libtipidee/tipidee_response_header_builtin.lo src/libtipidee/tipidee_response_header_common_put.lo src/libtipidee/tipidee_response_header_date_fmt.lo src/libtipidee/tipidee_response_status.lo src/libtipidee/tipidee_rql_read.lo src/libtipidee/tipidee_uri_parse.lo +endif +libtipidee.so.xyzzy: EXTRA_LIBS := -lskarnet +libtipidee.so.xyzzy: src/libtipidee/tipidee_chunked_read.lo src/libtipidee/tipidee_conf_free.lo src/libtipidee/tipidee_conf_get.lo src/libtipidee/tipidee_conf_get_argv.lo src/libtipidee/tipidee_conf_get_content_type.lo src/libtipidee/tipidee_conf_get_redirection.lo src/libtipidee/tipidee_conf_get_string.lo src/libtipidee/tipidee_conf_get_uint32.lo src/libtipidee/tipidee_conf_init.lo src/libtipidee/tipidee_headers_get_content_length.lo src/libtipidee/tipidee_headers_init.lo src/libtipidee/tipidee_headers_parse.lo src/libtipidee/tipidee_headers_search.lo src/libtipidee/tipidee_method_conv_table.lo src/libtipidee/tipidee_method_tonum.lo src/libtipidee/tipidee_method_tostr.lo src/libtipidee/tipidee_response_error.lo src/libtipidee/tipidee_response_header_builtin.lo src/libtipidee/tipidee_response_header_common_put.lo src/libtipidee/tipidee_response_header_date_fmt.lo src/libtipidee/tipidee_response_status.lo src/libtipidee/tipidee_rql_read.lo src/libtipidee/tipidee_uri_parse.lo +tipideed: EXTRA_LIBS := -lskarnet +tipideed: src/tipideed/tipideed.o src/tipideed/cgi.o src/tipideed/harden.o src/tipideed/log.o src/tipideed/options.o src/tipideed/regular.o src/tipideed/responses.o src/tipideed/send_file.o src/tipideed/tipideed.o src/tipideed/trace.o libtipidee.a.xyzzy +INTERNAL_LIBS := diff --git a/package/info b/package/info new file mode 100644 index 0000000..2702b3b --- /dev/null +++ b/package/info @@ -0,0 +1,4 @@ +package=tipidee +version=0.0.1.0 +category=web +package_macro_name=TIPIDEE diff --git a/package/modes b/package/modes new file mode 100644 index 0000000..3dcb680 --- /dev/null +++ b/package/modes @@ -0,0 +1,3 @@ +tipideed 0755 +tipidee-config-preprocess 0755 +tipidee-config 0755 diff --git a/package/targets.mak b/package/targets.mak new file mode 100644 index 0000000..1cf5bca --- /dev/null +++ b/package/targets.mak @@ -0,0 +1,8 @@ +BIN_TARGETS := \ +tipideed \ +tipidee-config + +LIBEXEC_TARGETS := \ +tipidee-config-preprocess + +LIB_DEFS := TIPIDEE=tipidee diff --git a/patch-for-solaris b/patch-for-solaris new file mode 100755 index 0000000..2d1296b --- /dev/null +++ b/patch-for-solaris @@ -0,0 +1,21 @@ +#!/usr/xpg4/bin/sh + +patchit () { + echo '#!/usr/xpg4/bin/sh' > $1.tmp + tail -n +2 $1 >> $1.tmp + mv -f $1.tmp $1 + chmod 755 $1 +} + +# Solaris doesn't understand POSIX.1-2008 either. +sed -e 's/XOPEN_SOURCE=700/XOPEN_SOURCE=600/' < configure > configure.tmp +mv -f configure.tmp configure + +patchit ./configure +patchit ./tools/install.sh +patchit ./tools/gen-deps.sh + +echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp +echo >> Makefile.tmp +cat Makefile >> Makefile.tmp +mv -f Makefile.tmp Makefile diff --git a/src/config/PARSING-config.txt b/src/config/PARSING-config.txt new file mode 100644 index 0000000..b1be2df --- /dev/null +++ b/src/config/PARSING-config.txt @@ -0,0 +1,56 @@ +global verbosity 1 +global read_timeout 60000 +global index index.cgi index.html + +content-type application/pdf .pdf +content-type text/plain .c .h .txt + + +domain example.com + +nph-prefix nph- + +redirect rickroll.html 307 https://www.youtube.com/watch?v=dQw4w9WgXcQ +redirect community/ 308 https://example.org/ + +cgi /cgi-bin/ +nph /cgi-bin/nph/ + +cgi /some/script +nph /some/otherscript + +noncgi /cgi-bin/file.html +noncgi /cgi-bin/directory + +file-type /source/ text/plain +file-type /source/file.html text/html + +basic-auth /protected.html +basic-auth /protected/ + + +class | 0 1 2 3 4 +st\ev | \0 space # \n other + +START | P np +00 | END SPACE COMMENT START WORD + +COMMENT | P +01 | END COMMENT COMMENT START COMMENT + +SPACE | P P np +02 | END SPACE COMMENT START WORD + +WORD | 0P 0 p 0P p +03 | END SPACE WORD START WORD + +END: 04 +X: 05 + +states: 3 bits +actions: 4 bits + +0x10 n new word +0x20 p push cur +0x40 0 push \0 +0x80 P process line diff --git a/src/config/PARSING-preprocess.txt b/src/config/PARSING-preprocess.txt new file mode 100644 index 0000000..e81c86e --- /dev/null +++ b/src/config/PARSING-preprocess.txt @@ -0,0 +1,38 @@ + + Automaton for the preprocessor: + + +class | 0 1 2 3 4 +st\ev | \0 \n ! space other + +START | print print print +0 | END START CMD NORMAL NORMAL + +NORMAL | print print print print +1 | END START NORMAL NORMAL NORMAL + +CMD | add +2 | END START IGNORE CMD1 CMD2 + +IGNORE | +3 | END START IGNORE IGNORE IGNORE + +CMD1 | add +4 | X X X CMD1 CMD2 + +CMD2 | idcmd add +5 | X X X ARG CMD2 + +ARG | add +6 | X X ARG1 ARG ARG1 + +ARG1 | proc proc add add add +7 | END START ARG1 ARG1 ARG1 + +states: 0-7 plus END and X -> 4 bits +actions: 4. -> 8 bits total, fits in a char. + +print 0x10 copies the character to stdout +add 0x20 adds the character to the processing string +idcmd 0x40 ids the processing string for an !include cmd +proc 0x80 gets the filename and procs the include diff --git a/src/config/PROTOCOL.txt b/src/config/PROTOCOL.txt new file mode 100644 index 0000000..ffeb72f --- /dev/null +++ b/src/config/PROTOCOL.txt @@ -0,0 +1,43 @@ +* Globals + +G:verbosity -> 4 bytes +G:read_timeout -> 4 bytes exit if waiting on client input for read_timeout ms +G:write_timeout -> 4 bytes die if waiting on flush to client for write_timeout ms +G:cgi_timeout -> 4 bytes 504 if CGI hasn't finished answering / die if NPH hasn't finished reading +G:max_request_body_length -> 4 bytes +G:max_cgi_body_length -> 4 bytes +G:index_file -> file1\0file2\0file3\0 list of files to attempt when URL is a directory + +They all need to exist, tipidee-config creates defaults. + + +* Content-Type + +T:extension -> string T:pdf -> application/pdf +or individual Content-Types in resource attributes + +tipidee-config hardcodes a number of default content-types, they +can be overridden. + + +* Individual redirection + +R:vres -> Xurl R:skarnet.org/rickroll.html -> Xhttps://www.youtube.com/watch?v=dQw4w9WgXcQ + X = '@' | redir_type (307=0, 308=1, 302=2, 301=3) + + +* Prefix redirection + +r:prefix -> Xurlprefix r:s6.org -> Xhttps://skarnet.org/software/s6 + X = '@' | redir_type (307=0, 308=1, 302=2, 301=3) + +* Resource attributes + +A:file -> Xstring X = '@' | 1 (iscgi) + if string nonempty: it's the content-type for the resource. If empty, default ctype + +a:prefix -> Xstring same, but for prefixes + +* NPH prefixes + +N:domain -> nphprefix N:skarnet.org -> nph- any CGI under skarnet.org starting with nph diff --git a/src/config/confnode.c b/src/config/confnode.c new file mode 100644 index 0000000..758e79d --- /dev/null +++ b/src/config/confnode.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include +#include + +#include +#include + +#include "tipidee-config-internal.h" + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") +#define diestorage() strerr_diefu2x(100, "add node to configuration tree", ": too much data") +#define diefilepos() strerr_diefu2x(100, "add node to configuration tree", ": file too large") + +void confnode_start (confnode *node, char const *key, size_t filepos, uint32_t line) +{ + size_t l = strlen(key) ; + size_t k = g.storage.len ; + if (!stralloc_catb(&g.storage, key, l + 1)) dienomem() ; + if (g.storage.len >= UINT32_MAX) diestorage() ; + if (filepos > UINT32_MAX) diefilepos() ; + node->key = k ; + node->keylen = l ; + node->data = g.storage.len ; + node->datalen = 0 ; + node->filepos = filepos ; + node->line = line ; +} + +void confnode_add (confnode *node, char const *s, size_t len) +{ + if (!stralloc_catb(&g.storage, s, len)) dienomem() ; + if (g.storage.len >= UINT32_MAX) strerr_diefu1x(100, "add node to configuration tree: too much data") ; + node->datalen += len ; +} diff --git a/src/config/conftree.c b/src/config/conftree.c new file mode 100644 index 0000000..fc0b5bc --- /dev/null +++ b/src/config/conftree.c @@ -0,0 +1,82 @@ +/* ISC license. */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "tipidee-config-internal.h" + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") + +static void *confnode_dtok (uint32_t d, void *data) +{ + return g.storage.s + GENSETDYN_P(confnode, (gensetdyn *)data, d)->key ; +} + +static int confnode_cmp (void const *a, void const *b, void *data) +{ + (void)data ; + return strcmp((char const *)a, (char const *)b) ; +} + +struct nodestore_s +{ + gensetdyn set ; + avltree tree ; +} ; + +static struct nodestore_s nodestore = \ +{ \ + .set = GENSETDYN_INIT(confnode, 8, 3, 8), \ + .tree = AVLTREE_INIT(8, 3, 8, &confnode_dtok, &confnode_cmp, &nodestore.set) \ +} ; + + +confnode const *conftree_search (char const *key) +{ + uint32_t i ; + return avltree_search(&nodestore.tree, key, &i) ? GENSETDYN_P(confnode const, &nodestore.set, i) : 0 ; +} + +void conftree_add (confnode const *node) +{ + uint32_t i ; + if (!gensetdyn_new(&nodestore.set, &i)) dienomem() ; + *GENSETDYN_P(confnode, &nodestore.set, i) = *node ; + if (!avltree_insert(&nodestore.tree, i)) dienomem() ; +} + +void conftree_update (confnode const *node) +{ + uint32_t i ; + if (avltree_search(&nodestore.tree, g.storage.s + node->key, &i)) + { + if (!avltree_delete(&nodestore.tree, g.storage.s + node->key)) dienomem() ; + *GENSETDYN_P(confnode, &nodestore.set, i) = *node ; + if (!avltree_insert(&nodestore.tree, i)) dienomem() ; + } + else return conftree_add(node) ; +} + +static int confnode_write (uint32_t d, unsigned int h, void *data) +{ + confnode *node = GENSETDYN_P(confnode, &nodestore.set, d) ; + cdbmaker *cm = data ; + (void)h ; + if ((g.storage.s[node->key] & ~0x20) == 'A') + { + g.storage.s[++node->data] |= '@' ; + node->datalen-- ; + } + return cdbmake_add(cm, g.storage.s + node->key, node->keylen, g.storage.s + node->data, node->datalen) ; +} + +int conftree_write (cdbmaker *cm) +{ + return avltree_iter(&nodestore.tree, &confnode_write, cm) ; +} diff --git a/src/config/defaults.c b/src/config/defaults.c new file mode 100644 index 0000000..5913796 --- /dev/null +++ b/src/config/defaults.c @@ -0,0 +1,106 @@ +/* ISC license. */ + +#include + +#include "tipidee-config-internal.h" + +struct defaults_s +{ + char const *key ; + char const *value ; + size_t vlen ; +} ; + +#define RECB(k, v, n) { .key = k, .value = v, .vlen = n } +#define REC(k, v) RECB(k, v, sizeof(v)) + +struct defaults_s const defaults[] = +{ + RECB("G:verbosity", "\0\0\0\001", 4), + RECB("G:read_timeout", "\0\0\0", 4), + RECB("G:write_timeout", "\0\0\0", 4), + RECB("G:cgi_timeout", "\0\0\0", 4), + RECB("G:max_request_body_length", "\0\0 ", 4), + RECB("G:max_cgi_body_length", "\0@\0", 4), + REC("G:index_file", "index.html"), + + REC("T:html", "text/html"), + REC("T:htm", "text/html"), + REC("T:txt", "text/plain"), + REC("T:h", "text/plain"), + REC("T:c", "text/plain"), + REC("T:cc", "text/plain"), + REC("T:cpp", "text/plain"), + REC("T:java", "text/plain"), + REC("T:mjs", "text/javascript"), + REC("T:css", "text/css"), + REC("T:csv", "text/csv"), + REC("T:doc", "application/msword"), + REC("T:docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + REC("T:js", "application/javascript"), + REC("T:jpg", "image/jpeg"), + REC("T:jpeg", "image/jpeg"), + REC("T:gif", "image/gif"), + REC("T:png", "image/png"), + REC("T:bmp", "image/bmp"), + REC("T:svg", "image/svg+xml"), + REC("T:tif", "image/tiff"), + REC("T:tiff", "image/tiff"), + REC("T:ico", "image/vnd.microsoft.icon"), + REC("T:au", "audio/basic"), + REC("T:aac", "audio/aac"), + REC("T:wav", "audio/wav"), + REC("T:mid", "audio/midi"), + REC("T:midi", "audio/midi"), + REC("T:mp3", "audio/mpeg"), + REC("T:ogg", "audio/ogg"), + REC("T:oga", "audio/ogg"), + REC("T:ogv", "video/ogg"), + REC("T:avi", "video/x-msvideo"), + REC("T:wmv", "video/x-ms-wmv"), + REC("T:qt", "video/quicktime"), + REC("T:mov", "video/quicktime"), + REC("T:mpe", "video/mpeg"), + REC("T:mpeg", "video/mpeg"), + REC("T:mp4", "video/mp4"), + REC("T:otf", "font/otf"), + REC("T:ttf", "font/ttf"), + REC("T:epub", "application/epub+zip"), + REC("T:jar", "application/java-archive"), + REC("T:json", "application/json"), + REC("T:jsonld", "application/ld+json"), + REC("T:pdf", "application/pdf"), + REC("T:ppt", "application/vnd.ms-powerpoint"), + REC("T:pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + REC("T:odp", "application/vnd.oasis.opendocument.presentation"), + REC("T:ods", "application/vnd.oasis.opendocument.spreadsheet"), + REC("T:odt", "application/vnd.oasis.opendocument.text"), + REC("T:oggx", "application/ogg"), + REC("T:rar", "application/vnd.rar"), + REC("T:rtf", "application/rtf"), + REC("T:sh", "application/x-sh"), + REC("T:tar", "application/x-tar"), + REC("T:xhtml", "application/xhtml+xml"), + REC("T:xls", "application/vnd.ms-excel"), + REC("T:xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + REC("T:xml", "application/xml"), + REC("T:xul", "application/vnd.mozilla.xul+xml"), + REC("T:zip", "application/zip"), + REC("T:7z", "application/x-7z-compressed"), + + RECB(0, 0, 0) +} ; + +void conf_defaults (void) +{ + for (struct defaults_s const *p = defaults ; p->key ; p++) + { + if (!conftree_search(p->key)) + { + confnode node ; + confnode_start(&node, p->key, 0, 0) ; + confnode_add(&node, p->value, p->vlen) ; + conftree_add(&node) ; + } + } +} diff --git a/src/config/deps-exe/tipidee-config b/src/config/deps-exe/tipidee-config new file mode 100644 index 0000000..85a9645 --- /dev/null +++ b/src/config/deps-exe/tipidee-config @@ -0,0 +1,6 @@ +confnode.o +conftree.o +defaults.o +lexparse.o +-lskarnet +${SPAWN_LIB} diff --git a/src/config/deps-exe/tipidee-config-preprocess b/src/config/deps-exe/tipidee-config-preprocess new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/config/deps-exe/tipidee-config-preprocess @@ -0,0 +1 @@ +-lskarnet diff --git a/src/config/lexparse.c b/src/config/lexparse.c new file mode 100644 index 0000000..f843f97 --- /dev/null +++ b/src/config/lexparse.c @@ -0,0 +1,443 @@ +/* ISC license. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "tipidee-config-internal.h" + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") +#define dietoobig() strerr_diefu1sys(100, "read configuration") + +typedef struct mdt_s mdt, *mdt_ref ; +struct mdt_s +{ + size_t filepos ; + uint32_t line ; + char linefmt[UINT32_FMT] ; +} ; +#define MDT_ZERO { .filepos = 0, .line = 0, .linefmt = "0" } + +struct globalkey_s +{ + char const *name ; + char const *key ; + uint32_t type ; +} ; + +static int globalkey_cmp (void const *a, void const *b) +{ + return strcmp((char const *)a, ((struct globalkey_s const *)b)->name) ; +} + +enum token_e +{ + T_BANG, + T_GLOBAL, + T_CONTENTTYPE, + T_DOMAIN, + T_NPHPREFIX, + T_REDIRECT, + T_CGI, + T_NONCGI, + T_NPH, + T_NONNPH, + T_BASICAUTH, + T_NOAUTH, + T_FILETYPE +} ; + +struct directive_s +{ + char const *s ; + enum token_e token ; +} ; + +static int directive_cmp (void const *a, void const *b) +{ + return strcmp((char const *)a, ((struct directive_s const *)b)->s) ; +} + +static void check_unique (char const *key, mdt const *md) +{ + confnode const *node = conftree_search(key) ; + if (node) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, node->line)] = 0 ; + strerr_diefn(1, 11, "duplicate key ", key, " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", previously defined", " in file ", g.storage.s + node->filepos, " line ", fmt) ; + } +} + +static void add_unique (char const *key, char const *value, size_t valuelen, mdt const *md) +{ + confnode node ; + check_unique(key, md) ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, value, valuelen) ; + conftree_add(&node) ; +} + +static inline void parse_global (char const *s, size_t const *word, size_t n, mdt const *md) +{ + static struct globalkey_s const globalkeys[] = + { + { .name = "cgi_timeout", .key = "G:cgi_timeout", .type = 0 }, + { .name = "index_file", .key = "G:index_file", .type = 1 }, + { .name = "max_cgi_body_length", .key = "G:max_cgi_body_length", .type = 0 }, + { .name = "max_request_body_length", .key = "G:max_request_body_length", .type = 0 }, + { .name = "read_timeout", .key = "G:read_timeout", .type = 0 }, + { .name = "verbosity", .key = "G:verbosity", .type = 0 }, + { .name = "write_timeout", .key = "G:write_timeout", .type = 0 } + } ; + struct globalkey_s *gl ; + if (n != 2) + strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "global", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + gl = bsearch(s + word[0], globalkeys, sizeof(globalkeys)/sizeof(struct globalkey_s), sizeof(struct globalkey_s), &globalkey_cmp) ; + if (!gl) strerr_dief6x(1, "unrecognized global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + switch (gl->type) + { + case 0 : /* 4 bytes */ + { + char pack[4] ; + uint32_t u ; + if (n != 2) strerr_dief7x(1, "too many", " arguments to global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!uint320_scan(s + word[1], &u)) + strerr_dief6x(1, "invalid (non-numeric) value for global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + uint32_pack_big(pack, u) ; + add_unique(gl->key, pack, 4, md) ; + break ; + } + case 1 : /* argv */ + { + confnode node ; + check_unique(gl->key, md) ; + confnode_start(&node, gl->key, md->filepos, md->line) ; + for (size_t i = 1 ; i < n ; i++) + confnode_add(&node, s + word[i], strlen(s + word[i]) + 1) ; + conftree_add(&node) ; + break ; + } + } +} + +static inline void parse_contenttype (char const *s, size_t const *word, size_t n, mdt const *md) +{ + char const *ct ; + if (n != 3) + strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + ct = s + *word++ ; + if (!strchr(ct, '/')) + strerr_dief6x(1, "Content-Type must include a slash, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + n-- ; + for (size_t i = 0 ; i < n ; i++) + { + size_t len = strlen(s + word[i]) ; + char key[len + 2] ; + if (s[word[i]] != '.') + strerr_dief6x(1, "file extensions must start with a dot, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + key[0] = 'T' ; + key[1] = ':' ; + memcpy(key + 2, s + word[i] + 1, len - 1) ; + key[len + 1] = 0 ; + add_unique(key, ct, strlen(ct) + 1, md) ; + } +} + +static inline void parse_redirect (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md) +{ + static uint32_t const codes[4] = { 307, 308, 302, 301 } ; + uint32_t code ; + uint32_t i = 0 ; + if (n != 3) + strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief6x(1, "redirection", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[word[0]] != '/') + strerr_dief5x(1, "redirected resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!uint320_scan(s + word[1], &code)) + strerr_dief6x(1, "invalid redirection code ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + for (; i < 4 ; i++) if (code == codes[i]) break ; + if (i >= 4) + strerr_dief6x(1, "redirection code ", "must be 301, 302, 307 or 308", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (strncmp(s + word[2], "http://", 7) && strncmp(s + word[2], "https://", 8)) + strerr_dief5x(1, "redirection target must be a full http:// or https:// target", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + confnode node ; + size_t urlen = strlen(s + word[0]) ; + char key[3 + domainlen + urlen] ; + if (s[word[0] + urlen - 1] == '/') { key[0] = 'r' ; urlen-- ; } else key[0] = 'R' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + word[0], urlen) ; + key[2 + domainlen + urlen] = 0 ; + check_unique(key, md) ; + confnode_start(&node, key, md->filepos, md->line) ; + key[0] = '@' | i ; + confnode_add(&node, &key[0], 1) ; + confnode_add(&node, s + word[2], strlen(s + word[2]) + 1) ; + conftree_add(&node) ; + } +} + +static void parse_bitattr (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md, unsigned int bit, int set) +{ + static char const *attr[3][2] = { { "noncgi", "cgi" }, { "nonnph", "nph", }, { "noauth", "basic-auth" } } ; + uint8_t mask = (uint8_t)0x01 << bit ; + if (n != 1) + strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", attr[bit][set], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief7x(1, "resource attribute ", "definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[*word] != '/') + strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + confnode const *oldnode ; + size_t arglen = strlen(s + *word) ; + char key[3 + domainlen + arglen] ; + if (s[*word + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + *word, arglen) ; + key[2 + domainlen + arglen] = 0 ; + oldnode = conftree_search(key) ; + if (oldnode) + if (g.storage.s[oldnode->data] & mask) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, oldnode->line)] = 0 ; + strerr_diefn(1, 13, "resource attribute ", attr[bit][set], " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ; + } + else + { + g.storage.s[oldnode->data] |= mask ; + if (set) g.storage.s[oldnode->data + 1] |= mask ; + else g.storage.s[oldnode->data + 1] &= ~mask ; + } + else + { + confnode node ; + char val[3] = { mask, set ? mask : 0, 0 } ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, val, 3) ; + conftree_add(&node) ; + } + } +} + +static inline void parse_filetype (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md) +{ + if (n != 2) + strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "file-type", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief7x(1, "file-type", " definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[word[0]] != '/') + strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + confnode const *oldnode ; + size_t arglen = strlen(s + word[0]) ; + char key[3 + domainlen + arglen] ; + if (s[word[0] + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + *word, arglen) ; + key[2 + domainlen + arglen] = 0 ; + oldnode = conftree_search(key) ; + if (oldnode) + { + if (g.storage.s[oldnode->data] & 0x80) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, oldnode->line)] = 0 ; + strerr_diefn(1, 12, "file-type", " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ; + } + + else + { + confnode node ; + char val[2] = { g.storage.s[oldnode->data] | 0x80, g.storage.s[oldnode->data + 1] } ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, val, 2) ; + confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ; + conftree_update(&node) ; + } + } + else + { + confnode node ; + char val[2] = { 0x80, 0x00 } ; + confnode_start(&node, key, md->filepos, md->line) ; + confnode_add(&node, val, 2) ; + confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ; + conftree_add(&node) ; + } + } +} + +static inline void process_line (char const *s, size_t const *word, size_t n, stralloc *domain, mdt *md) +{ + static struct directive_s const directives[] = + { + { .s = "!", .token = T_BANG }, + { .s = "basic-auth", .token = T_BASICAUTH }, + { .s = "cgi", .token = T_CGI }, + { .s = "content-type", .token = T_CONTENTTYPE }, + { .s = "domain", .token = T_DOMAIN }, + { .s = "file-type", .token = T_FILETYPE }, + { .s = "global", .token = T_GLOBAL }, + { .s = "no-auth", .token = T_NOAUTH }, + { .s = "noncgi", .token = T_NONCGI }, + { .s = "nonnph", .token = T_NONNPH }, + { .s = "nph", .token = T_NPH }, + { .s = "nph-prefix", .token = T_NPHPREFIX }, + { .s = "redirect", .token = T_REDIRECT }, + } ; + struct directive_s const *directive ; + char const *word0 ; + if (!n--) return ; + word0 = s + *word++ ; + directive = bsearch(word0, directives, sizeof(directives)/sizeof(struct directive_s), sizeof(struct directive_s), &directive_cmp) ; + if (!directive) + strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + switch (directive->token) + { + case T_BANG : + { + size_t len, r, w ; + if (n != 2 || !uint320_scan(s + word[0], &md->line)) + strerr_dief5x(101, "can't happen: invalid ! control directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + len = strlen(s + word[1]) ; + if (!stralloc_readyplus(&g.storage, len + 1)) dienomem() ; + if (!string_unquote(g.storage.s + g.storage.len, &w, s + word[1], len, &r)) + strerr_dief7sys(101, "can't happen: unable to unquote ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", error reported is") ; + g.storage.s[g.storage.len + w++] = 0 ; + md->filepos = g.storage.len ; + g.storage.len += w ; + break ; + } + case T_GLOBAL : + parse_global(s, word, n, md) ; + break ; + case T_CONTENTTYPE : + parse_contenttype(s, word, n, md) ; + break ; + case T_DOMAIN : + if (n != 1) + strerr_dief8x(1, "too", n > 1 ? "many" : "few", " arguments to directive ", "domain", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + domain->len = 0 ; + if (!stralloc_cats(domain, s + *word)) dienomem() ; + while (domain->len && (domain->s[domain->len - 1] == '/' || domain->s[domain->len - 1] == '.')) domain->len-- ; + break ; + case T_NPHPREFIX : + if (n != 1) + strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", "nph-prefix", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain->s) + strerr_dief6x(1, "nph prefix definition", "without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (strchr(s + *word, '/')) strerr_dief5x(1, "invalid / in nph-prefix argument", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + { + char key[3 + domain->len] ; + key[0] = 'N' ; + key[1] = ':' ; + memcpy(key + 2, domain->s, domain->len) ; + key[2 + domain->len] = 0 ; + add_unique(key, s + *word, strlen(s + *word) + 1, md) ; + } + break ; + case T_REDIRECT : + parse_redirect(s, word, n, domain->s, domain->len, md) ; + break ; + case T_CGI : + parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 1) ; + break ; + case T_NONCGI : + parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 0) ; + break ; + case T_NPH : + parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 1) ; + break ; + case T_NONNPH : + parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 0) ; + break ; + case T_BASICAUTH : + strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ; + parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 1) ; + break ; + case T_NOAUTH : + strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ; + parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 0) ; + break ; + case T_FILETYPE : + parse_filetype(s, word, n, domain->s, domain->len, md) ; + break ; + } +} + +static inline uint8_t cclass (char c) +{ + switch (c) + { + case 0 : return 0 ; + case ' ' : + case '\t' : + case '\f' : + case '\r' : return 1 ; + case '#' : return 2 ; + case '\n' : return 3 ; + default : return 4 ; + } +} + +static inline char next (buffer *b, mdt const *md) +{ + char c ; + ssize_t r = buffer_get(b, &c, 1) ; + if (r == -1) strerr_diefu1sys(111, "read from preprocessor") ; + if (!r) return 0 ; + if (!c) strerr_dief5x(1, "null character", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + return c ; +} + +void conf_lexparse (buffer *b, char const *ifile) +{ + static uint8_t const table[4][5] = /* see PARSING.txt */ + { + { 0x04, 0x02, 0x01, 0x80, 0x33 }, + { 0x04, 0x01, 0x01, 0x80, 0x01 }, + { 0x84, 0x02, 0x01, 0x80, 0x33 }, + { 0xc4, 0x42, 0x23, 0xc0, 0x23 } + } ; + stralloc sa = STRALLOC_ZERO ; + genalloc words = GENALLOC_ZERO ; /* size_t */ + stralloc domain = STRALLOC_ZERO ; + mdt md = MDT_ZERO ; + uint8_t state = 0 ; + if (!stralloc_catb(&g.storage, ifile, strlen(ifile) + 1)) dienomem() ; + while (state < 0x04) + { + char c = next(b, &md) ; + uint8_t what = table[state][cclass(c)] ; + state = what & 0x07 ; + if (what & 0x10) if (!genalloc_catb(size_t, &words, &sa.len, 1)) dienomem() ; + if (what & 0x20) if (!stralloc_catb(&sa, &c, 1)) dienomem() ; + if (what & 0x40) if (!stralloc_0(&sa)) dienomem() ; + if (what & 0x80) + { + process_line(sa.s, genalloc_s(size_t, &words), genalloc_len(size_t, &words), &domain, &md) ; + genalloc_setlen(size_t, &words, 0) ; + sa.len = 0 ; + md.line++ ; + md.linefmt[uint32_fmt(md.linefmt, md.line)] = 0 ; + } + } + stralloc_free(&domain) ; + genalloc_free(size_t, &words) ; + stralloc_free(&sa) ; +} diff --git a/src/config/tipidee-config-internal.h b/src/config/tipidee-config-internal.h new file mode 100644 index 0000000..e274f94 --- /dev/null +++ b/src/config/tipidee-config-internal.h @@ -0,0 +1,59 @@ +/* ISC license. */ + +#ifndef TIPIDEE_CONFIG_INTERNAL_H +#define TIPIDEE_CONFIG_INTERNAL_H + +#include +#include + +#include +#include +#include + +typedef struct confnode_s confnode, *confnode_ref ; +struct confnode_s +{ + uint32_t key ; + uint32_t keylen ; + uint32_t data ; + uint32_t datalen ; + uint32_t filepos ; + uint32_t line ; +} ; +#define CONFNODE_ZERO { .key = 0, .keylen = 0, .data = 0, .datalen = 0 } + +struct global_s +{ + stralloc storage ; +} ; +#define GLOBAL_ZERO { .storage = STRALLOC_ZERO } + +extern struct global_s g ; + + + /* confnode */ + +extern void confnode_start (confnode *, char const *, size_t, uint32_t) ; +extern void confnode_add (confnode *, char const *, size_t) ; +#define confnode_adds(node, s) confnode_add(node, (s), strlen(s)) +#define confnode_add0(node) confnode_add((node), "", 1) + + + /* conftree */ + +extern confnode const *conftree_search (char const *) ; +extern void conftree_add (confnode const *) ; +extern void conftree_update (confnode const *) ; +extern int conftree_write (cdbmaker *) ; + + + /* lexparse */ + +extern void conf_lexparse (buffer *, char const *) ; + + + /* defaults */ + +extern void conf_defaults (void) ; + +#endif diff --git a/src/config/tipidee-config-preprocess.c b/src/config/tipidee-config-preprocess.c new file mode 100644 index 0000000..6ac4812 --- /dev/null +++ b/src/config/tipidee-config-preprocess.c @@ -0,0 +1,270 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "tipidee-config-preprocess file" +#define dieusage() strerr_dieusage(100, USAGE) +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") ; + +static stralloc sa = STRALLOC_ZERO ; +static genalloc ga = GENALLOC_ZERO ; /* size_t */ + + + /* Name storage */ + +static stralloc namesa = STRALLOC_ZERO ; + +static void *name_dtok (uint32_t pos, void *aux) +{ + return ((stralloc *)aux)->s + pos + 1 ; +} + +static int name_cmp (void const *a, void const *b, void *aux) +{ + (void)aux ; + return strcmp((char const *)a, (char const *)b) ; +} + +static avltree namemap = AVLTREE_INIT(8, 3, 8, &name_dtok, &name_cmp, &namesa) ; + + + /* Directory sorting */ + +static char const *dname_cmp_base ; +static int dname_cmp (void const *a, void const *b) +{ + return strcmp(dname_cmp_base + *(size_t *)a, dname_cmp_base + *(size_t *)b) ; +} + + + /* Recursive inclusion functions */ + +static void includefromhere (char const *) ; + +static inline void includecwd (void) +{ + DIR *dir ; + size_t sabase = sa.len ; + size_t gabase = genalloc_len(size_t, &ga) ; + if (sagetcwd(&sa) < 0 || !stralloc_0(&sa)) dienomem() ; + dir = opendir(".") ; + if (!dir) strerr_diefu2sys(111, "opendir ", sa.s + sabase) ; + + for (;;) + { + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (d->d_name[0] == '.') continue ; + if (!genalloc_catb(size_t, &ga, &sa.len, 1)) dienomem() ; + if (!stralloc_catb(&sa, d->d_name, strlen(d->d_name)+1)) dienomem() ; + } + dir_close(dir) ; + if (errno) strerr_diefu2sys(111, "readdir ", sa.s + sabase) ; + + dname_cmp_base = sa.s ; + qsort(genalloc_s(size_t, &ga) + gabase, genalloc_len(size_t, &ga) - gabase, sizeof(size_t), &dname_cmp) ; + + for (size_t i = 0 ; i < genalloc_len(size_t, &ga) ; i++) + includefromhere(sa.s + genalloc_s(size_t, &ga)[gabase + i]) ; + + genalloc_setlen(size_t, &ga, gabase) ; + sa.len = sabase ; +} + +static void include (char const *file) +{ + size_t sabase = sa.len ; + size_t filelen = strlen(file) ; + if (!sadirname(&sa, file, filelen) || !stralloc_0(&sa)) dienomem() ; + if (chdir(sa.s + sabase) < 0) strerr_diefu2sys(111, "chdir to ", sa.s + sabase) ; + sa.len = sabase ; + if (!sabasename(&sa, file, filelen)) dienomem() ; + { + char fn[sa.len + 1 - sabase] ; + memcpy(fn, sa.s + sabase, sa.len - sabase) ; + fn[sa.len - sabase] = 0 ; + sa.len = sabase ; + includefromhere(fn) ; + } +} + +static inline int idcmd (char const *s) +{ + static char const *commands[] = + { + "include", + "includedir", + "included:", + 0 + } ; + for (char const **p = commands ; *p ; p++) + if (!strcmp(s, *p)) return p - commands ; + return -1 ; +} + +static inline unsigned char cclass (char c) +{ + static unsigned char const classtable[34] = "0444444443144344444444444444444432" ; + return (unsigned char)c < 34 ? classtable[(unsigned char)c] - '0' : 4 ; +} + +static void includefromhere (char const *file) +{ + static unsigned char const table[8][5] = + { + { 0x08, 0x10, 0x02, 0x11, 0x11 }, + { 0x08, 0x10, 0x11, 0x11, 0x11 }, + { 0x08, 0x00, 0x03, 0x04, 0x25 }, + { 0x08, 0x00, 0x03, 0x03, 0x03 }, + { 0x09, 0x09, 0x09, 0x04, 0x25 }, + { 0x09, 0x09, 0x09, 0x46, 0x25 }, + { 0x0a, 0x0a, 0x07, 0x06, 0x27 }, + { 0x88, 0x80, 0x27, 0x27, 0x27 } + } ; + size_t sabase = sa.len ; + size_t namesabase = namesa.len ; + size_t sastart ; + int cmd = -1 ; + int fd ; + buffer b ; + uint32_t d ; + uint32_t line = 1 ; + char buf[4096] ; + char linefmt[UINT32_FMT] = "1" ; + unsigned char state = 0 ; + + if (!stralloc_catb(&namesa, "\004", 1) || sarealpath(&namesa, file) < 0 || !stralloc_0(&namesa)) dienomem() ; + if (avltree_search(&namemap, namesa.s + namesabase + 1, &d)) + { + if (namesa.s[d] & 0x04) + strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included in a cycle") ; + if (!(namesa.s[d] & 0x02)) + strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included twice but does not declare !included: unique or !included: multiple") ; + namesa.len = namesabase ; + if (namesa.s[d] & 0x01) return ; + } + else + { + if (namesabase > UINT32_MAX) + strerr_dief3x(3, "in ", namesa.s + d + 1, ": too many, too long filenames") ; + d = namesabase ; + if (!avltree_insert(&namemap, d)) dienomem() ; + } + + if (!string_quote(&sa, namesa.s + d + 1, strlen(namesa.s + d + 1))) dienomem() ; + if (!stralloc_0(&sa)) dienomem() ; + + sastart = sa.len ; + fd = open_readb(file) ; + if (fd < 0) strerr_diefu2sys(111, "open ", namesa.s + d + 1) ; + buffer_init(&b, &buffer_read, fd, buf, 4096) ; + + if (buffer_put(buffer_1, "! 0 ", 4) < 4 + || buffer_puts(buffer_1, sa.s + sabase) < 0 + || buffer_put(buffer_1, "\n", 1) < 1) + strerr_diefu1sys(111, "write to stdout") ; + + while (state < 8) + { + uint16_t what ; + char c = 0 ; + if (buffer_get(&b, &c, 1) < 0) strerr_diefu2sys(111, "read from ", namesa.s + d + 1) ; + what = table[state][cclass((unsigned char)c)] ; + state = what & 0x000f ; + if (what & 0x0010) if (buffer_put(buffer_1, &c, 1) < 1) strerr_diefu1sys(111, "write to stdout") ; + if (what & 0x0020) if (!stralloc_catb(&sa, &c, 1)) dienomem() ; + if (what & 0x0040) + { + if (!stralloc_0(&sa)) dienomem() ; + cmd = idcmd(sa.s + sastart) ; + if (cmd == -1) + strerr_dief6x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": unrecognized directive: ", sa.s + sastart) ; + sa.len = sastart ; + } + if (what & 0x0080) + { + if (!stralloc_0(&sa)) dienomem() ; + switch (cmd) + { + case 2 : + if (!strcmp(sa.s + sastart, "unique")) namesa.s[d] |= 3 ; + else if (!strcmp(sa.s + sastart, "multiple")) namesa.s[d] |= 2 ; + else strerr_dief6x(3, "in ", namesa.s + d + 1, " line ", linefmt, "invalid !included: argument: ", sa.s + sastart) ; + break ; + case 1 : + case 0 : + { + int fdhere = open2(".", O_RDONLY | O_DIRECTORY) ; + if (fdhere == -1) + strerr_dief3sys(111, "in ", namesa.s + d + 1, ": unable to open base directory: ") ; + if (cmd & 1) + { + if (chdir(sa.s + sastart) == -1) + strerr_dief6sys(111, "in ", namesa.s + d + 1, " line ", linefmt, ": unable to chdir to ", sa.s + sastart) ; + includecwd() ; + } + else include(sa.s + sastart) ; + if (fchdir(fdhere) == -1) + strerr_dief4sys(111, "in ", namesa.s + d + 1, ": unable to fchdir back after including ", sa.s + sastart) ; + fd_close(fdhere) ; + if (buffer_put(buffer_1, "! ", 2) < 2 + || buffer_puts(buffer_1, linefmt) < 0 + || buffer_put(buffer_1, " ", 1) < 1 + || buffer_puts(buffer_1, sa.s + sabase) < 0 + || buffer_put(buffer_1, "\n", 1) < 1) + strerr_diefu1sys(111, "write to stdout") ; + break ; + } + } + sa.len = sastart ; + } + if (c == '\n' && state <= 8) linefmt[uint32_fmt(linefmt, ++line)] = 0 ; + } + if (state > 8) strerr_dief5x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": syntax error: invalid ! line") ; + fd_close(fd) ; + sa.len = sabase ; + namesa.s[d] &= ~0x04 ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "tipidee-config-preprocess" ; + { + subgetopt l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "", &l) ; + if (opt == -1) break ; + switch (opt) + { + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (!argc) dieusage() ; + + include(argv[0]) ; + if (!buffer_flush(buffer_1)) + strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} diff --git a/src/config/tipidee-config.c b/src/config/tipidee-config.c new file mode 100644 index 0000000..be13e39 --- /dev/null +++ b/src/config/tipidee-config.c @@ -0,0 +1,135 @@ +/* ISC license. */ + +#include +#include +#include +#include /* rename() */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "tipidee-config-internal.h" + +#define USAGE "tipidee-config [ -i textfile ] [ -o cdbfile ] [ -m mode ]" +#define dieusage() strerr_dieusage(100, USAGE) + +struct global_s g = GLOBAL_ZERO ; + +static pid_t pid = 0 ; + +static void sigchld_handler (int sig) +{ + (void)sig ; + for (;;) + { + int wstat ; + pid_t r = wait_nohang(&wstat) ; + if (r == -1 && errno != ECHILD) strerr_diefu1sys(111, "wait") ; + else if (r <= 0) break ; + else if (r == pid) + { + if (WIFEXITED(wstat) && !WEXITSTATUS(wstat)) pid = 0 ; + else _exit(wait_estatus(wstat)) ; + } + } +} + +static inline void conf_output (char const *ofile, unsigned int omode) +{ + int fdw ; + cdbmaker cm = CDBMAKER_ZERO ; + size_t olen = strlen(ofile) ; + char otmp[olen + 8] ; + memcpy(otmp, ofile, olen) ; + memcpy(otmp + olen, ":XXXXXX", 8) ; + fdw = mkstemp(otmp) ; + if (fdw == -1) strerr_diefu3sys(111, "open ", otmp, " for writing") ; + if (coe(fdw) == -1) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "coe ", otmp) ; + } + if (!cdbmake_start(&cm, fdw)) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "cdmake_start ", otmp) ; + } + if (!conftree_write(&cm)) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "write config tree into ", otmp) ; + } + if (!cdbmake_finish(&cm)) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "cdbmake_finish ", otmp) ; + } + if (fsync(fdw) == -1) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "fsync ", otmp) ; + } + if (fchmod(fdw, omode & 0755) == -1) + { + unlink_void(otmp) ; + strerr_diefu2sys(111, "fchmod ", otmp) ; + } + if (rename(otmp, ofile) == -1) + { + unlink_void(otmp) ; + strerr_diefu4sys(111, "rename ", otmp, " to ", ofile) ; + } +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + char const *ifile = "/etc/tipidee.conf" ; + char const *ofile = "/etc/tipidee.conf.cdb" ; + unsigned int omode = 0644 ; + + PROG = "tipidee-config" ; + { + subgetopt l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "i:o:m:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'i' : ifile = l.arg ; break ; + case 'o' : ofile = l.arg ; break ; + case 'm' : if (!uint0_oscan(l.arg, &omode)) dieusage() ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + { + int fdr ; + buffer b ; + char buf[4096] ; + sig_block(SIGCHLD) ; + if (!sig_catch(SIGCHLD, &sigchld_handler)) + strerr_diefu1sys(111, "install SIGCHLD handler") ; + { + char const *ppargv[3] = { TIPIDEE_LIBEXECPREFIX "tipidee-config-preprocess", ifile, 0 } ; + pid = child_spawn1_pipe(ppargv[0], ppargv, envp, &fdr, 1) ; + if (!pid) strerr_diefu2sys(errno == ENOENT ? 127 : 126, "spawn ", ppargv[0]) ; + } + sig_unblock(SIGCHLD) ; + buffer_init(&b, &buffer_read, fdr, buf, 4096) ; + conf_lexparse(&b, ifile) ; + } + conf_defaults() ; + conf_output(ofile, omode) ; + return 0 ; +} diff --git a/src/include/tipidee/body.h b/src/include/tipidee/body.h new file mode 100644 index 0000000..fc8bd9a --- /dev/null +++ b/src/include/tipidee/body.h @@ -0,0 +1,25 @@ +/* ISC license. */ + +#ifndef TIPIDEE_BODY_H +#define TIPIDEE_BODY_H + +#include +#include + +#include +#include +#include + +typedef enum tipidee_transfercoding_e tipidee_transfercoding, *tipidee_transfercoding_ref ; +enum tipidee_transfercoding_e +{ + TIPIDEE_TRANSFERCODING_NONE = 0, + TIPIDEE_TRANSFERCODING_FIXED, + TIPIDEE_TRANSFERCODING_CHUNKED, + TIPIDEE_TRANSFERCODING_UNKNOWN +} ; + +extern int tipidee_chunked_read (buffer *, stralloc *, size_t, tain const *, tain *) ; +#define tipidee_chunked_read_g(b, sa, max, deadline) tipidee_chunked_read(b, sa, max, (deadline), &STAMP) + +#endif diff --git a/src/include/tipidee/conf.h b/src/include/tipidee/conf.h new file mode 100644 index 0000000..bc66d76 --- /dev/null +++ b/src/include/tipidee/conf.h @@ -0,0 +1,44 @@ +/* ISC license. */ + +#ifndef TIPIDEE_CONF_H +#define TIPIDEE_CONF_H + +#include +#include + +#include +#include + +#include + +#define TIPIDEE_CONF_KEY_MAXLEN 0x1000U + +typedef struct tipidee_conf_s tipidee_conf, *tipidee_conf_ref ; +struct tipidee_conf_s +{ + cdb c ; +} ; +#define TIPIDEE_CONF_ZERO { .c = CDB_ZERO } + +typedef struct tipidee_redirection_s tipidee_redirection, *tipidee_redirection_ref ; +struct tipidee_redirection_s +{ + char const *location ; + char const *sub ; + uint32_t type : 2 ; +} ; +#define TIPIDEE_REDIRECTION_ZERO { .location = 0, .sub = 0, .type = 0 } + +extern void tipidee_conf_free (tipidee_conf *) ; +extern int tipidee_conf_init (tipidee_conf *, char const *) ; + +extern int tipidee_conf_get (tipidee_conf const *, char const *, cdb_data *) ; +extern char const *tipidee_conf_get_string (tipidee_conf const *, char const *) ; +extern int tipidee_conf_get_uint32 (tipidee_conf const *, char const *, uint32_t *) ; +extern unsigned int tipidee_conf_get_argv (tipidee_conf const *, char const *, char const **, unsigned int, size_t *) ; + +extern char const *tipidee_conf_get_docroot (tipidee_conf const *, tipidee_uri const *, uint16_t) ; +extern int tipidee_conf_get_redirection (tipidee_conf const *, char const *, size_t, tipidee_redirection *) ; +extern char const *tipidee_conf_get_content_type (tipidee_conf const *, char const *) ; + +#endif diff --git a/src/include/tipidee/headers.h b/src/include/tipidee/headers.h new file mode 100644 index 0000000..420b4c9 --- /dev/null +++ b/src/include/tipidee/headers.h @@ -0,0 +1,38 @@ +/* ISC license. */ + +#ifndef TIPIDEE_HEADERS_H +#define TIPIDEE_HEADERS_H + +#include +#include + +#include +#include +#include +#include + +#define TIPIDEE_HEADERS_MAX 32 + +typedef struct tipidee_headers_s tipidee_headers, *tipidee_headers_ref ; +struct tipidee_headers_s +{ + char *buf ; + size_t max ; + size_t len ; + disize list[TIPIDEE_HEADERS_MAX] ; + avltreen map ; + uint32_t map_freelist[TIPIDEE_HEADERS_MAX] ; + avlnode map_storage[TIPIDEE_HEADERS_MAX] ; + uint32_t n ; +} ; + +extern void tipidee_headers_init (tipidee_headers *, char *, size_t) ; + +extern int tipidee_headers_parse_nb (buffer *, tipidee_headers *, disize *, uint32_t *) ; +extern int tipidee_headers_timed_parse (buffer *, tipidee_headers *, tain const *, tain *) ; +#define tipidee_headers_timed_parse_g(b, hdr, deadline) tipidee_headers_timed_parse(b, hdr, (deadline), &STAMP) + +extern char const *tipidee_headers_search (tipidee_headers const *, char const *) ; +extern ssize_t tipidee_headers_get_content_length (tipidee_headers const *) ; + +#endif diff --git a/src/include/tipidee/method.h b/src/include/tipidee/method.h new file mode 100644 index 0000000..05123e8 --- /dev/null +++ b/src/include/tipidee/method.h @@ -0,0 +1,32 @@ +/* ISC license. */ + +#ifndef TIPIDEE_METHOD_H +#define TIPIDEE_METHOD_H + +typedef enum tipidee_method_e tipidee_method, *tipidee_method_ref ; +enum tipidee_method_e +{ + TIPIDEE_METHOD_GET = 0, + TIPIDEE_METHOD_HEAD, + TIPIDEE_METHOD_OPTIONS, + TIPIDEE_METHOD_POST, + TIPIDEE_METHOD_PUT, + TIPIDEE_METHOD_DELETE, + TIPIDEE_METHOD_TRACE, + TIPIDEE_METHOD_CONNECT, + TIPIDEE_METHOD_PRI, + TIPIDEE_METHOD_UNKNOWN +} ; + +typedef struct tipidee_method_conv_s tipidee_method_conv, *tipidee_method_conv_ref ; +struct tipidee_method_conv_s +{ + tipidee_method num ; + char const *str ; +} ; + +extern tipidee_method_conv const *tipidee_method_conv_table ; +extern char const *tipidee_method_tostr (tipidee_method) ; +extern tipidee_method tipidee_method_tonum (char const *) ; + +#endif diff --git a/src/include/tipidee/response.h b/src/include/tipidee/response.h new file mode 100644 index 0000000..e0a6177 --- /dev/null +++ b/src/include/tipidee/response.h @@ -0,0 +1,36 @@ +/* ISC license. */ + +#ifndef TIPIDEE_RESPONSE_H +#define TIPIDEE_RESPONSE_H + +#include +#include + +#include +#include +#include +#include + +#include + +typedef struct tipidee_response_header_builtin_s tipidee_response_header_builtin, *tipidee_response_header_builtin_ref ; +struct tipidee_response_header_builtin_s +{ + char const *key ; + char const *value ; +} ; + +extern size_t tipidee_response_status (buffer *, tipidee_rql const *, unsigned int, char const *) ; +#define tipidee_response_status_line(b, rql, line) tipidee_response_status(b, rql, 0, (line)) + +extern size_t tipidee_response_header_date_fmt (char *, size_t, tain const *) ; +#define tipidee_response_header_date_fmt_g(buf, max) tipidee_response_header_date_fmt(buf, (max), &STAMP) +extern size_t tipidee_response_header_common_put (buffer *, uint32_t, tain const *) ; +#define tipidee_response_header_common_put_g(b, options) tipidee_response_header_common_put(b, (options), &STAMP) + +extern size_t tipidee_response_error (buffer *, tipidee_rql const *, char const *, char const *, uint32_t) ; + +extern tipidee_response_header_builtin const *tipidee_response_header_builtin_table ; +extern char const *tipidee_response_header_builtin_search (char const *) ; + +#endif diff --git a/src/include/tipidee/rql.h b/src/include/tipidee/rql.h new file mode 100644 index 0000000..471c4ad --- /dev/null +++ b/src/include/tipidee/rql.h @@ -0,0 +1,31 @@ +/* ISC license. */ + +#ifndef TIPIDEE_RQL_H +#define TIPIDEE_RQL_H + +#include +#include + +#include +#include + +typedef struct tipidee_rql_s tipidee_rql, *tipidee_rql_ref ; +struct tipidee_rql_s +{ + tipidee_method m ; + unsigned int http_major ; + unsigned int http_minor ; + tipidee_uri uri ; +} ; +#define TIPIDEE_RQL_ZERO \ +{ \ + .m = TIPIDEE_METHOD_UNKNOWN, \ + .http_major = 0, \ + .http_minor = 0, \ + .uri = TIPIDEE_URI_ZERO \ +} + +extern int tipidee_rql_read (buffer *, char *, size_t, size_t *, tipidee_rql *, tain const *, tain *) ; +#define tipidee_rql_read_g(b, buf, max, w, rql, deadline) tipidee_rql_read(b, buf, max, w, rql, (deadline), &STAMP) + +#endif diff --git a/src/include/tipidee/tipidee.h b/src/include/tipidee/tipidee.h new file mode 100644 index 0000000..ddd1348 --- /dev/null +++ b/src/include/tipidee/tipidee.h @@ -0,0 +1,15 @@ +/* ISC license. */ + +#ifndef TIPIDEE_H +#define TIPIDEE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/src/include/tipidee/uri.h b/src/include/tipidee/uri.h new file mode 100644 index 0000000..6e5148b --- /dev/null +++ b/src/include/tipidee/uri.h @@ -0,0 +1,31 @@ +/* ISC license. */ + +#ifndef TIPIDEE_URI_H +#define TIPIDEE_URI_H + +#include +#include + +typedef struct tipidee_uri_s tipidee_uri, *tipidee_uri_ref ; +struct tipidee_uri_s +{ + char const *host ; + char const *path ; + char const *query ; + size_t lastslash ; + uint16_t port ; + uint8_t https : 1 ; +} ; +#define TIPIDEE_URI_ZERO \ +{ \ + .host = 0, \ + .path = 0, \ + .query = 0, \ + .lastslash = 0, \ + .port = 0, \ + .https = 0 \ +} + +extern size_t tipidee_uri_parse (char *, size_t, char const *, tipidee_uri *) ; + +#endif diff --git a/src/libtipidee/deps-lib/tipidee b/src/libtipidee/deps-lib/tipidee new file mode 100644 index 0000000..d218af1 --- /dev/null +++ b/src/libtipidee/deps-lib/tipidee @@ -0,0 +1,24 @@ +tipidee_chunked_read.o +tipidee_conf_free.o +tipidee_conf_get.o +tipidee_conf_get_argv.o +tipidee_conf_get_content_type.o +tipidee_conf_get_redirection.o +tipidee_conf_get_string.o +tipidee_conf_get_uint32.o +tipidee_conf_init.o +tipidee_headers_get_content_length.o +tipidee_headers_init.o +tipidee_headers_parse.o +tipidee_headers_search.o +tipidee_method_conv_table.o +tipidee_method_tonum.o +tipidee_method_tostr.o +tipidee_response_error.o +tipidee_response_header_builtin.o +tipidee_response_header_common_put.o +tipidee_response_header_date_fmt.o +tipidee_response_status.o +tipidee_rql_read.o +tipidee_uri_parse.o +-lskarnet diff --git a/src/libtipidee/tipidee_chunked_read.c b/src/libtipidee/tipidee_chunked_read.c new file mode 100644 index 0000000..66d5d80 --- /dev/null +++ b/src/libtipidee/tipidee_chunked_read.c @@ -0,0 +1,41 @@ +/* ISC license. */ + +#include +#include + +#include +#include +#include + +#include + +#include + +int tipidee_chunked_read (buffer *b, stralloc *sa, size_t maxlen, tain const *deadline, tain *stamp) +{ + char line[512] ; + for (;;) + { + size_t chunklen, pos, w = 0 ; + ssize_t r = timed_getlnmax(b, line, 512, &w, '\n', deadline, stamp) ; + if (r < 0) return 0 ; + if (!r) return (errno = EPIPE, 0) ; + pos = size_scan(line, &chunklen) ; + if (!pos) return (errno = EPROTO, 0) ; + if (!memchr("\r\n; \t", line[pos], 5)) return (errno = EPROTO, 0) ; + if (!chunklen) break ; + if (sa->len + chunklen > maxlen) return (errno = EMSGSIZE, 0) ; + if (!stralloc_readyplus(sa, chunklen)) return 0 ; + if (buffer_timed_get(b, sa->s + sa->len, chunklen, deadline, stamp) < chunklen) return 0 ; + sa->len += chunklen ; + } + for (;;) + { + size_t w = 0 ; + ssize_t r = timed_getlnmax(b, line, 512, &w, '\n', deadline, stamp) ; + if (r < 0) return 0 ; + if (!r) return (errno = EPIPE, 0) ; + if (w == 1 || (w == 2 && line[0] == '\r')) break ; + } + return 1 ; +} diff --git a/src/libtipidee/tipidee_conf_free.c b/src/libtipidee/tipidee_conf_free.c new file mode 100644 index 0000000..e708d89 --- /dev/null +++ b/src/libtipidee/tipidee_conf_free.c @@ -0,0 +1,10 @@ +/* ISC license. */ + +#include + +#include + +void tipidee_conf_free (tipidee_conf *conf) +{ + cdb_free(&conf->c) ; +} diff --git a/src/libtipidee/tipidee_conf_get.c b/src/libtipidee/tipidee_conf_get.c new file mode 100644 index 0000000..866ec99 --- /dev/null +++ b/src/libtipidee/tipidee_conf_get.c @@ -0,0 +1,22 @@ +/* ISC license. */ + +#include +#include + +#include +#include + +#include + +int tipidee_conf_get (tipidee_conf const *conf, char const *key, cdb_data *data) +{ + size_t keylen = strlen(key) ; + if (keylen > TIPIDEE_CONF_KEY_MAXLEN) return (errno = EINVAL, 0) ; + LOLDEBUG("tipidee_conf_get: looking up %s", key) ; + switch (cdb_find(&conf->c, data, key, keylen)) + { + case -1 : return (errno = EILSEQ, 0) ; + case 0 : return (errno = ENOENT, 0) ; + default : return 1 ; + } +} diff --git a/src/libtipidee/tipidee_conf_get_argv.c b/src/libtipidee/tipidee_conf_get_argv.c new file mode 100644 index 0000000..0dd016e --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_argv.c @@ -0,0 +1,32 @@ +/* ISC license. */ + +#include +#include + +#include + +#include + +#include + +unsigned int tipidee_conf_get_argv (tipidee_conf const *conf, char const *key, char const **argv, unsigned int max, size_t *maxlen) +{ + cdb_data data ; + size_t curlen = 0 ; + unsigned int n = 0, pos = 0 ; + if (!tipidee_conf_get(conf, key, &data)) return 0 ; + if (data.s[data.len-1]) return (errno = EPROTO, 0) ; + while (pos < data.len) + { + size_t len ; + if (n >= max) return (errno = E2BIG, 0) ; + argv[n++] = data.s + pos ; + len = strlen(data.s + pos) ; + if (len > curlen) curlen = len ; + pos += len + 1 ; + } + if (n >= max) return (errno = E2BIG, 0) ; + argv[n++] = 0 ; + if (maxlen) *maxlen = curlen ; + return n ; +} diff --git a/src/libtipidee/tipidee_conf_get_content_type.c b/src/libtipidee/tipidee_conf_get_content_type.c new file mode 100644 index 0000000..7ee8866 --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_content_type.c @@ -0,0 +1,22 @@ +/* ISC license. */ + +#include +#include + +#include + +char const *tipidee_conf_get_content_type (tipidee_conf const *conf, char const *res) +{ + char const *ext = strrchr(res, '.') ; + if (ext && !strchr(ext, '/')) + { + char const *value = 0 ; + size_t extlen = strlen(ext+1) ; + char key[3 + extlen] ; + key[0] = 'T' ; key[1] = ':' ; + memcpy(key + 2, ext + 1, extlen + 1) ; + value = tipidee_conf_get_string(conf, key) ; + if (value || errno != ENOENT) return value ; + } + return "application/octet-stream" ; +} diff --git a/src/libtipidee/tipidee_conf_get_redirection.c b/src/libtipidee/tipidee_conf_get_redirection.c new file mode 100644 index 0000000..62ada34 --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_redirection.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include +#include + +#include + +#include + +int tipidee_conf_get_redirection (tipidee_conf const *conf, char const *res, size_t docrootlen, tipidee_redirection *r) +{ + size_t reslen = strlen(res) ; + size_t l = 2 + reslen ; + char const *v = 0 ; + char key[3 + reslen] ; + key[0] = 'R' ; key[1] = ':' ; + memcpy(key + 2, res, reslen) ; + key[2 + reslen] = '/' ; + errno = ENOENT ; + while (!v) + { + if (errno != ENOENT) return -1 ; + while (l > 2 + docrootlen && key[l] != '/') l-- ; + if (l <= 2 + docrootlen) break ; + key[l--] = 0 ; + key[0] = 'r' ; + v = tipidee_conf_get_string(conf, key) ; + } + if (!v) return 0 ; + if (v[0] < '@' || v[0] > 'C') return (errno = EPROTO, -1) ; + r->type = v[0] & ~'@' ; + r->location = v+1 ; + r->sub = res + l - 2 ; + return 1 ; +} diff --git a/src/libtipidee/tipidee_conf_get_string.c b/src/libtipidee/tipidee_conf_get_string.c new file mode 100644 index 0000000..0ea93cd --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_string.c @@ -0,0 +1,17 @@ +/* ISC license. */ + +#include + +#include + +#include + +#include + +char const *tipidee_conf_get_string (tipidee_conf const *conf, char const *key) +{ + cdb_data data ; + if (!tipidee_conf_get(conf, key, &data)) return 0 ; + if (data.s[data.len-1]) { errno = EPROTO ; return 0 ; } + return data.s ; +} diff --git a/src/libtipidee/tipidee_conf_get_uint32.c b/src/libtipidee/tipidee_conf_get_uint32.c new file mode 100644 index 0000000..ad8b21b --- /dev/null +++ b/src/libtipidee/tipidee_conf_get_uint32.c @@ -0,0 +1,19 @@ +/* ISC license. */ + +#include + +#include +#include + +#include + +#include + +int tipidee_conf_get_uint32 (tipidee_conf const *conf, char const *key, uint32_t *value) +{ + cdb_data data ; + if (!tipidee_conf_get(conf, key, &data)) return 0 ; + if (data.len != 4) return (errno = EPROTO, 0) ; + uint32_unpack_big(data.s, value) ; + return 1 ; +} diff --git a/src/libtipidee/tipidee_conf_init.c b/src/libtipidee/tipidee_conf_init.c new file mode 100644 index 0000000..3d2e119 --- /dev/null +++ b/src/libtipidee/tipidee_conf_init.c @@ -0,0 +1,10 @@ +/* ISC license. */ + +#include + +#include + +int tipidee_conf_init (tipidee_conf *conf, char const *file) +{ + return cdb_init(&conf->c, file) ; +} diff --git a/src/libtipidee/tipidee_headers_get_content_length.c b/src/libtipidee/tipidee_headers_get_content_length.c new file mode 100644 index 0000000..0b29ece --- /dev/null +++ b/src/libtipidee/tipidee_headers_get_content_length.c @@ -0,0 +1,17 @@ +/* ISC license. */ + +#include +#include + +#include + +#include + +ssize_t tipidee_headers_get_content_length (tipidee_headers const *hdr) +{ + size_t n ; + char const *x = tipidee_headers_search(hdr, "Content-Length") ; + if (!x) return 0 ; + if (!size0_scan(x, &n) || n > SSIZE_MAX) return -1 ; + return n ; +} diff --git a/src/libtipidee/tipidee_headers_init.c b/src/libtipidee/tipidee_headers_init.c new file mode 100644 index 0000000..6c2d336 --- /dev/null +++ b/src/libtipidee/tipidee_headers_init.c @@ -0,0 +1,29 @@ +/* ISC license. */ + +#include +#include + +#include + +#include + +static void *tipidee_headers_dtok (uint32_t d, void *data) +{ + tipidee_headers *hdr = data ; + return hdr->buf + hdr->list[d].left ; +} + +static int tipidee_headers_cmp (void const *a, void const *b, void *data) +{ + (void)data ; + return strcasecmp(a, b) ; +} + +void tipidee_headers_init (tipidee_headers *hdr, char *buf, size_t max) +{ + hdr->buf = buf ; + hdr->max = max ; + hdr->len = 0 ; + hdr->n = 0 ; + avltreen_init(&hdr->map, hdr->map_storage, hdr->map_freelist, TIPIDEE_HEADERS_MAX, &tipidee_headers_dtok, &tipidee_headers_cmp, hdr) ; +} diff --git a/src/libtipidee/tipidee_headers_parse.c b/src/libtipidee/tipidee_headers_parse.c new file mode 100644 index 0000000..57108bb --- /dev/null +++ b/src/libtipidee/tipidee_headers_parse.c @@ -0,0 +1,210 @@ +/* ISC license. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +// #include + +#include + +/* + +Reads header lines, separates into \0-terminated keys+values. +key is at hdr->buf + hdr->list[i].left +value is at hdr->buf + hdr->list[i].right +Compresses linear whitespace. +Does not unquote strings/comments in values. + + +st\ev 0 1 2 3 4 5 6 7 + CTL CR LF LWS : special normal 8bit + +00 kp +START X END? END X X X K X + +01 +END? X X END X X X X X + +02 zv p +K X X X X V1 X K X + +03 p p p p +V1 X V1?? V1? V1 V V V V + +04 +V1?? X X V1? X X X X X + +05 zn zn znkp +V1? X END? END V1 X X K X + +06 s s s p p p p +V X V2?? V2? V2 V V V V + +07 p p p p +V2 X V2?? V2? V2 V V V V + +08 +V2?? X X V2? X X X X X + +09 mzn mzn mznkp +V2? X END? END V2 X X K X + +END = 0a, X = 0b + +0x4000 s: write space +0x2000 m: go back one char +0x1000 z: write \0 +0x0800 n: cut key/value pair, prepare next +0x0400 k: start of key +0x0200 v: start of value +0x0100 p: write current char + +states: 4 bits, actions: 7 bits + +*/ + + +struct tainp_s +{ + tain const *deadline ; + tain *stamp ; +} ; + +typedef int get1_func (buffer *, char *, struct tainp_s *) ; +typedef get1_func *get1_func_ref ; + +static int get1_timed (buffer *b, char *c, struct tainp_s *d) +{ + return buffer_timed_get(b, c, 1, d->deadline, d->stamp) ; +} + +static int get1_notimed (buffer *b, char *c, struct tainp_s *data) +{ + (void)data ; + return buffer_get(b, c, 1) == 1 ; +} + +static uint8_t cclass (char c) +{ + static uint8_t const ctable[128] = "00000000032001000000000000000000365666665566566566666666664565655666666666666666666666666665556666666666666666666666666666656560" ; + return c & 0x80 ? 7 : ctable[(uint8_t)c] - '0' ; +} + +static int needs_processing (char const *s) +{ + if (!strcasecmp(s, "Set-Cookie")) return 0 ; + if (str_start(s, "X-")) return 0 ; + return 1 ; +} + +static int tipidee_headers_parse_with (buffer *b, tipidee_headers *hdr, get1_func_ref next, struct tainp_s *data, disize *header, uint32_t *state) +{ + static uint16_t const table[10][8] = + { + { 0x000b, 0x0001, 0x000a, 0x000b, 0x000b, 0x000b, 0x0502, 0x000b }, + { 0x000b, 0x000b, 0x000a, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x000b, 0x000b, 0x000b, 0x000b, 0x1203, 0x000b, 0x0102, 0x000b }, + { 0x000b, 0x0004, 0x0005, 0x0003, 0x0106, 0x0106, 0x0106, 0x0106 }, + { 0x000b, 0x000b, 0x0005, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x000b, 0x1801, 0x180a, 0x0003, 0x000b, 0x000b, 0x1d02, 0x000b }, + { 0x000b, 0x4008, 0x4009, 0x4007, 0x0106, 0x0106, 0x0106, 0x0106 }, + { 0x000b, 0x0008, 0x0009, 0x0007, 0x0106, 0x0106, 0x0106, 0x0106 }, + { 0x000b, 0x000b, 0x0009, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b }, + { 0x000b, 0x3801, 0x380a, 0x0007, 0x000b, 0x000b, 0x3d02, 0x000b }, + } ; + while (*state < 0x0a) + { + uint16_t c ; + char cur ; + if (!(*next)(b, &cur, data)) + return errno == ETIMEDOUT ? 408 : error_isagain(errno) ? -2 : -1 ; + c = table[*state][cclass(cur)] ; +/* + { + + char s[2] = { cur, 0 } ; + LOLDEBUG("tipidee_headers_parse_with: state %hhu, event %s, newstate %hhu, actions %s%s%s%s%s%s%s", + *state, + cur == '\r' ? "\\r" : cur == '\n' ? "\\n" : s, + c & 0x0f, + c & 0x4000 ? "s" : "", + c & 0x2000 ? "m" : "", + c & 0x1000 ? "z" : "", + c & 0x0800 ? "n" : "", + c & 0x0400 ? "k" : "", + c & 0x0200 ? "v" : "", + c & 0x0100 ? "p" : "" + ) ; + } +*/ + *state = c & 0x0f ; + if (c & 0x4000) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = ' ' ; } + if (c & 0x2000) hdr->len-- ; + if (c & 0x1000) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = 0 ; } + if (c & 0x0800) + { + uint32_t prev ; + if (hdr->n >= TIPIDEE_HEADERS_MAX) return 413 ; + hdr->list[hdr->n] = *header ; + if (needs_processing(hdr->buf + header->left)) + { +// LOLDEBUG("tipidee_headers_parse_with: n: adding header %u - key %zu (%s), value %zu (%s)", hdr->n, header->left, hdr->buf + header->left, header->right, hdr->buf + header->right) ; + if (avltreen_search(&hdr->map, hdr->buf + header->left, &prev)) + { + size_t start = hdr->list[prev+1].left ; + if (prev+1 == hdr->n) + { + hdr->buf[start - 1] = ',' ; + hdr->buf[start] = ' ' ; + memcpy(hdr->buf + start + 1, hdr->buf + header->right, hdr->len - header->right) ; + } + else + { + size_t len = header->left - start ; + size_t offset = hdr->len - header->right + 1 ; + char tmp[len] ; + memcpy(tmp, hdr->buf + start, len) ; + hdr->buf[start - 1] = ',' ; + hdr->buf[start] = ' ' ; + memcpy(hdr->buf + start + 1, hdr->buf + header->right, hdr->len - header->right) ; + memcpy(hdr->buf + start + offset, tmp, len) ; + for (uint32_t i = prev + 1 ; i < hdr->n ; i++) + { + hdr->list[i].left += offset ; + hdr->list[i].right += offset ; + } + } + hdr->len -= header->right - header->left - 1 ; + hdr->n-- ; + } + else if (!avltreen_insert(&hdr->map, hdr->n)) return 500 ; + } + hdr->n++ ; + } + if (c & 0x0400) header->left = hdr->len ; + if (c & 0x0200) header->right = hdr->len ; + if (c & 0x0100) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = cur ; } + } + if (*state > 0x0a) return 400 ; + return 0 ; +} + +int tipidee_headers_timed_parse (buffer *b, tipidee_headers *hdr, tain const *deadline, tain *stamp) +{ + struct tainp_s d = { .deadline = deadline, .stamp = stamp } ; + disize header = DISIZE_ZERO ; + uint32_t state = 0 ; + return tipidee_headers_parse_with(b, hdr, &get1_timed, &d, &header, &state) ; +} + +int tipidee_headers_parse_nb (buffer *b, tipidee_headers *hdr, disize *header, uint32_t *state) +{ + return tipidee_headers_parse_with(b, hdr, &get1_notimed, 0, header, state) ; +} diff --git a/src/libtipidee/tipidee_headers_search.c b/src/libtipidee/tipidee_headers_search.c new file mode 100644 index 0000000..553ef65 --- /dev/null +++ b/src/libtipidee/tipidee_headers_search.c @@ -0,0 +1,13 @@ +/* ISC license. */ + +#include + +#include + +#include + +char const *tipidee_headers_search (tipidee_headers const *hdr, char const *key) +{ + uint32_t i ; + return avltreen_search(&hdr->map, key, &i) ? hdr->buf + hdr->list[i].right : 0 ; +} diff --git a/src/libtipidee/tipidee_method_conv_table.c b/src/libtipidee/tipidee_method_conv_table.c new file mode 100644 index 0000000..892e5b5 --- /dev/null +++ b/src/libtipidee/tipidee_method_conv_table.c @@ -0,0 +1,19 @@ +/* ISC license. */ + +#include + +static tipidee_method_conv const table[] = +{ + { .num = TIPIDEE_METHOD_GET, .str = "GET" }, + { .num = TIPIDEE_METHOD_HEAD, .str = "HEAD" }, + { .num = TIPIDEE_METHOD_OPTIONS, .str = "OPTIONS" }, + { .num = TIPIDEE_METHOD_POST, .str = "POST" }, + { .num = TIPIDEE_METHOD_PUT, .str = "PUT" }, + { .num = TIPIDEE_METHOD_DELETE, .str = "DELETE" }, + { .num = TIPIDEE_METHOD_TRACE, .str = "TRACE" }, + { .num = TIPIDEE_METHOD_CONNECT, .str = "CONNECT" }, + { .num = TIPIDEE_METHOD_PRI, .str = "PRI" }, + { .num = TIPIDEE_METHOD_UNKNOWN, .str = 0 } +} ; + +tipidee_method_conv const *tipidee_method_conv_table = table ; diff --git a/src/libtipidee/tipidee_method_tonum.c b/src/libtipidee/tipidee_method_tonum.c new file mode 100644 index 0000000..7c88e45 --- /dev/null +++ b/src/libtipidee/tipidee_method_tonum.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include + +#include + +tipidee_method tipidee_method_tonum (char const *s) +{ + tipidee_method_conv const *p = tipidee_method_conv_table ; + for (; p->str ; p++) if (!strcmp(s, p->str)) break ; + return p->num ; +} diff --git a/src/libtipidee/tipidee_method_tostr.c b/src/libtipidee/tipidee_method_tostr.c new file mode 100644 index 0000000..8a3831d --- /dev/null +++ b/src/libtipidee/tipidee_method_tostr.c @@ -0,0 +1,8 @@ +/* ISC license. */ + +#include + +char const *tipidee_method_tostr (tipidee_method m) +{ + return m < TIPIDEE_METHOD_UNKNOWN ? tipidee_method_conv_table[m].str : 0 ; +} diff --git a/src/libtipidee/tipidee_response_error.c b/src/libtipidee/tipidee_response_error.c new file mode 100644 index 0000000..e2687a4 --- /dev/null +++ b/src/libtipidee/tipidee_response_error.c @@ -0,0 +1,41 @@ +/* ISC license. */ + +#include + +#include +#include + +#include +#include +#include + +size_t tipidee_response_error (buffer *b, tipidee_rql const *rql, char const *rsl, char const *text, uint32_t options) +{ + size_t n = 0 ; + static char const txt1[] = "\n" ; + static char const txt2[] = "\n\n

" ; + static char const txt3[] = "

\n

\n" ; + static char const txt4[] = "\n

\n\n\n" ; + n += tipidee_response_status_line(b, rql, rsl) ; + n += tipidee_response_header_common_put_g(buffer_1, options) ; + if (!(options & 2)) + { + char fmt[SIZE_FMT] ; + n += buffer_putsnoflush(buffer_1, "Content-Type: text/html; charset=UTF-8\r\n") ; + n += buffer_putsnoflush(buffer_1, "Content-Length: ") ; + n += buffer_putnoflush(buffer_1, fmt, size_fmt(fmt, sizeof(txt1) + sizeof(txt2) + sizeof(txt3) + sizeof(txt4) - 4 + 2 * strlen(rsl) + strlen(text))) ; + n += buffer_putnoflush(buffer_1, "\r\n", 2) ; + } + n += buffer_putnoflush(buffer_1, "\r\n", 2) ; + if (rql->m != TIPIDEE_METHOD_HEAD) + { + n += buffer_putsnoflush(buffer_1, txt1) ; + n += buffer_putsnoflush(buffer_1, rsl) ; + n += buffer_putsnoflush(buffer_1, txt2) ; + n += buffer_putsnoflush(buffer_1, rsl) ; + n += buffer_putsnoflush(buffer_1, txt3) ; + n += buffer_putsnoflush(buffer_1, text) ; + n += buffer_putsnoflush(buffer_1, txt4) ; + } + return n ; +} diff --git a/src/libtipidee/tipidee_response_header_builtin.c b/src/libtipidee/tipidee_response_header_builtin.c new file mode 100644 index 0000000..0125cb8 --- /dev/null +++ b/src/libtipidee/tipidee_response_header_builtin.c @@ -0,0 +1,40 @@ +/* ISC license. */ + +#include +#include + +#include +#include + +static tipidee_response_header_builtin const tipidee_response_header_builtin_table_[] = +{ + { .key = "Accept-Ranges", .value = "none" }, + { .key = "Cache-Control", .value = "private" }, + { .key = "Content-Security-Policy", .value = "default-src 'self'; style-src 'self' 'unsafe-inline';" }, + { .key = "Referrer-Policy", .value = "no-referrer-when-downgrade" }, + { .key = "Server", .value = "tipidee/" TIPIDEE_VERSION }, + { .key = "Vary", .value = "Accept-Encoding" }, + { .key = "X-Content-Type-Options", .value = "nosniff" }, + { .key = "X-Frame-Options", .value = "DENY" }, + { .key = "X-XSS-Protection", .value = "1; mode=block" }, + { .key = 0, .value = 0 }, +} ; + +tipidee_response_header_builtin const *tipidee_response_header_builtin_table = tipidee_response_header_builtin_table_ ; + +static int tipidee_response_header_builtin_cmp (void const *a, void const *b) +{ + return strcmp((char const *)a, ((tipidee_response_header_builtin const *)b)->key) ; +} + +char const *tipidee_response_header_builtin_search (char const *key) +{ + tipidee_response_header_builtin const *p = bsearch( + key, + tipidee_response_header_builtin_table_, + sizeof(tipidee_response_header_builtin_table_) / sizeof(tipidee_response_header_builtin) - 1, + sizeof(tipidee_response_header_builtin), + &tipidee_response_header_builtin_cmp) ; + return p ? p->value : 0 ; +} + diff --git a/src/libtipidee/tipidee_response_header_common_put.c b/src/libtipidee/tipidee_response_header_common_put.c new file mode 100644 index 0000000..8352ba9 --- /dev/null +++ b/src/libtipidee/tipidee_response_header_common_put.c @@ -0,0 +1,23 @@ +/* ISC license. */ + +#include + +#include + +#include +#include + +size_t tipidee_response_header_common_put (buffer *b, uint32_t options, tain const *stamp) +{ + char fmt[128] ; + size_t m = buffer_putnoflush(b, fmt, tipidee_response_header_date_fmt(fmt, 128, stamp)) ; + for (tipidee_response_header_builtin const *p = tipidee_response_header_builtin_table ; p->key ; p++) + { + m += buffer_putsnoflush(b, p->key) ; + m += buffer_putnoflush(b, ": ", 2) ; + m += buffer_putsnoflush(b, p->value) ; + m += buffer_putnoflush(b, "\r\n", 2) ; + } + if (options & 1) m += buffer_putsnoflush(b, "Connection: close\r\n") ; + return m ; +} diff --git a/src/libtipidee/tipidee_response_header_date_fmt.c b/src/libtipidee/tipidee_response_header_date_fmt.c new file mode 100644 index 0000000..df19673 --- /dev/null +++ b/src/libtipidee/tipidee_response_header_date_fmt.c @@ -0,0 +1,24 @@ +/* ISC license. */ + +#include +#include + +#include +#include + +#include + +size_t tipidee_response_header_date_fmt (char *s, size_t max, tain const *stamp) +{ + size_t m = 0, l ; + struct tm tm ; + if (m + 6 > max) return 0 ; + if (!localtm_from_tai(&tm, tain_secp(stamp), 0)) return 0 ; + memcpy(s, "Date: ", 6) ; m += 6 ; + l = strftime(s + m, max - m, "%a, %d %b %Y %T GMT", &tm) ; + if (!l) return 0 ; + m += l ; + if (m + 2 > max) return 0 ; + s[m++] = '\r' ; s[m++] = '\n' ; + return m ; +} diff --git a/src/libtipidee/tipidee_response_status.c b/src/libtipidee/tipidee_response_status.c new file mode 100644 index 0000000..aedec39 --- /dev/null +++ b/src/libtipidee/tipidee_response_status.c @@ -0,0 +1,27 @@ +/* ISC license. */ + +#include +#include + +#include + +size_t tipidee_response_status (buffer *b, tipidee_rql const *rql, unsigned int status, char const *line) +{ + size_t n = 0 ; + char fmt[UINT_FMT] ; + n += buffer_putnoflush(b, "HTTP/", 5) ; + n += buffer_putnoflush(b, fmt, uint_fmt(fmt, rql->http_major ? rql->http_major : 1)) ; + n += buffer_putnoflush(b, ".", 1) ; + n += buffer_putnoflush(b, fmt, uint_fmt(fmt, rql->http_major ? rql->http_minor : 1)) ; + n += buffer_putnoflush(b, " ", 1) ; + if (status) + { + char fmt[UINT_FMT] ; + size_t m = uint_fmt(fmt, status) ; + n += buffer_putnoflush(b, fmt, m) ; + n += buffer_putnoflush(b, " ", 1) ; + } + n += buffer_putsnoflush(b, line) ; + n += buffer_putnoflush(b, "\r\n", 2) ; + return n ; +} diff --git a/src/libtipidee/tipidee_rql_read.c b/src/libtipidee/tipidee_rql_read.c new file mode 100644 index 0000000..f3508cf --- /dev/null +++ b/src/libtipidee/tipidee_rql_read.c @@ -0,0 +1,85 @@ +/* ISC license. */ + +#include +#include +#include + +#include +#include +#include +#include +// #include + +#include +#include +#include + +static inline uint8_t tokenize_cclass (char c) +{ + switch (c) + { + case '\0' : return 0 ; + case ' ' : + case '\t' : return 1 ; + default : return 2 ; + } +} + +static inline int rql_tokenize (char *s, size_t *tab) +{ + uint8_t const table[2][3] = + { + { 0x02, 0x00, 0x11 }, + { 0x02, 0x20, 0x01 } + } ; + size_t i = 0 ; + unsigned int tokens = 0 ; + uint8_t state = 0 ; + for (; state < 2 ; i++) + { + uint8_t c = table[state][tokenize_cclass(s[i])] ; + state = c & 3 ; + if (c & 0x10) + { + if (tokens >= 3) goto err ; + tab[tokens++] = i ; + } + if (c & 0x20) s[i] = 0 ; + } + return 1 ; + err: + return 0 ; +} + +static inline int get_version (char const *in, tipidee_rql *rql) +{ + size_t l ; + if (strncmp(in, "HTTP/", 5)) return 0 ; + in += 5 ; + l = uint_scan(in, &rql->http_major) ; + if (!l) return 0 ; + in += l ; + if (*in++ != '.') return 0 ; + return !!uint0_scan(in, &rql->http_minor) ; +} + +int tipidee_rql_read (buffer *b, char *buf, size_t max, size_t *w, tipidee_rql *rql, tain const *deadline, tain *stamp) +{ + size_t pos[3] = { 0 } ; + if (timed_getlnmax(b, buf, max, &pos[0], '\n', deadline, stamp) <= 0) return -1 ; + buf[--pos[0]] = 0 ; + if (buf[pos[0] - 1] == '\r') buf[--pos[0]] = 0 ; +// LOLDEBUG("tipidee_rql_read: timed_getlnmax: len is %zu, line is %s", pos[0], buf) ; + if (!rql_tokenize(buf, pos)) return 400 ; +// LOLDEBUG("tipidee_rql_read: method: %s, version: %s, uri to parse: %s", buf + pos[0], buf + pos[2], buf + pos[1]) ; + rql->m = tipidee_method_tonum(buf + pos[0]) ; + if (rql->m == TIPIDEE_METHOD_UNKNOWN) return 400 ; + if (!get_version(buf + pos[2], rql)) return 400 ; + if (rql->m != TIPIDEE_METHOD_OPTIONS || strcmp(buf + pos[1], "*")) + { + size_t l = tipidee_uri_parse(buf, max, buf + pos[1], &rql->uri) ; + if (!l) return 400 ; + *w = l ; + } + return 0 ; +} diff --git a/src/libtipidee/tipidee_uri_parse.c b/src/libtipidee/tipidee_uri_parse.c new file mode 100644 index 0000000..10b0f91 --- /dev/null +++ b/src/libtipidee/tipidee_uri_parse.c @@ -0,0 +1,184 @@ +/* ISC license. */ + +#include +#include + +#include +#include +#include +#include + +#include + + +/* + + Decodes an URI. + Accepts absolute (http and https) and local, decodes %-encoding up to ? query. + +st\ev 0 1 2 3 4 5 6 7 8 9 a b c d e + \0 invalid % ? / : @ h t p s 0-9 a-f other delim + +00 Pp +START X X X X PATH? X X H X X X X X X X + +01 +H X X X X X X X X HT X X X X X X + +02 +HT X X X X X X X X HTT X X X X X X + +03 +HTT X X X X X X X X X HTTP X X X X X + +04 +HTTP X X X X X AUTH X X X X HTTPS X X X X + +05 s +HTTPS X X X X X AUTH X X X X X X X X X + +06 +AUTH X X X X AUTH/ X X X X X X X X X X + +07 +AUTH/ X X X X HOST X X X X X X X X X X + +08 H Hp Hp Hp Hp Hp Hp Hp Hp +HOST X X HQ H1 X X X H1 H1 H1 H1 H1 H1 H1 X + +09 p a a +HQ X X H1 X X X X X X X X HQ1 HQ1 X X + +0a ab ab +HQ1 X X X X X X X X X X X H1 H1 X X + +0b p zPp zm p p p p p p p +H1 END X HQ H1 PATH PORT X H1 H1 H1 H1 H1 H1 H1 X + +0c Pp p +PORT X X X X PATH X X X X X X PORT1 X X X + +0d zc zcPp p +PORT1 END X X X PATH X X X X X X PORT1 X X X + +0e p zQ p p p p p p p p p p +PATH END X Q QUERY PATH PATH PATH PATH PATH PATH PATH PATH PATH PATH X + +0f p a a +Q X X PATH X X X X X X X X Q1 Q1 X X + +10 ab ab +Q1 X X X X X X X X X X X PATH PATH X X + +11 p p p p p p p p p p p p p p +QUERY END X QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY + +12 p zQ p p p p p p p p p +PATH? END X Q QUERY X PATH PATH PATH PATH PATH PATH PATH PATH PATH X + +st\ev 0 1 2 3 4 5 6 7 8 9 a b c d e + \0 invalid % ? / : @ h t p s 0-9 a-f other delim + +END = 13, X = 14 + +0x8000 s ssl +0x4000 H start host +0x2000 z print \0 +0x1000 m mark +0x0800 c scan port from mark, reset to mark +0x0400 P start path +0x0200 p print cur +0x0100 Q start query +0x0080 a push num +0x0040 b decode num, print num, reinit + +*/ + +static inline uint8_t uridecode_cclass (char c) +{ + static uint8_t const table[128] = "01111111111111111111111111111111161162>>>>>=>==4;;;;;;;;;;5>>=>36<<<<<<====================>1>==1<<<<<<=7=======9==:8======111=1" ; + return c < 0 ? 1 : table[(uint8_t)c] - '0' ; +} + +size_t tipidee_uri_parse (char *out, size_t max, char const *in, tipidee_uri *uri) +{ + static uint16_t const table[19][15] = + { + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0612, 0x0014, 0x0014, 0x0001, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0002, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0003, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0004, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0006, 0x0014, 0x0014, 0x0014, 0x0014, 0x0005, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x8006, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0007, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0008, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x4009, 0x420b, 0x0014, 0x0014, 0x0014, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x0014 }, + { 0x0014, 0x0014, 0x020b, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x008a, 0x008a, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x00cb, 0x00cb, 0x0014, 0x0014 }, + { 0x0213, 0x0014, 0x0009, 0x000b, 0x260e, 0x300c, 0x0014, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x060e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x020d, 0x0014, 0x0014, 0x0014 }, + { 0x2813, 0x0014, 0x0014, 0x0014, 0x2e0e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x020d, 0x0014, 0x0014, 0x0014 }, + { 0x0213, 0x0014, 0x000f, 0x2111, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x0014 }, + { 0x0014, 0x0014, 0x020e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0090, 0x0090, 0x0014, 0x0014 }, + { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x00ce, 0x00ce, 0x0014, 0x0014 }, + { 0x0213, 0x0014, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211 }, + { 0x0213, 0x0014, 0x000f, 0x2111, 0x0014, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x0014 } + } ; + size_t w = 0, lastslash = 0, mark = 0 ; + char const *host = 0 ; + char const *path = 0 ; + char const *query = 0 ; + uint16_t port = 0 ; + uint16_t state = 0 ; + unsigned char decoded = 0 ; + uint8_t ssl = 0 ; + for (; state < 0x13 ; in++) + { + uint16_t c = table[state][uridecode_cclass(*in)] ; +/* + LOLDEBUG("tipidee_uri_parse: state %hu, event %c, newstate %hu, actions %s%s%s%s%s%s%s%s%s%s", state, *in, c & 0x1f, + c & 0x8000 ? "s" : "", + c & 0x4000 ? "H" : "", + c & 0x2000 ? "z" : "", + c & 0x1000 ? "m" : "", + c & 0x0800 ? "c" : "", + c & 0x0400 ? "P" : "", + c & 0x0200 ? "p" : "", + c & 0x0100 ? "Q" : "", + c & 0x0080 ? "a" : "", + c & 0x0040 ? "b" : "" + ) ; +*/ + state = c & 0x1f ; + if (c & 0x8000) ssl = 1 ; + if (c & 0x4000) host = out + w ; + if (c & 0x2000) { if (w >= max) return 0 ; out[w++] = 0 ; } + if (c & 0x1000) mark = w ; + if (c & 0x0800) { if (!uint160_scan(out + mark, &port)) return 0 ; w = mark ; } + if (c & 0x0400) path = out + w ; + if (c & 0x0200) { if (w >= max) return 0 ; out[w++] = *in ; } + if (c & 0x0100) query = out + w ; + if (c & 0x0080) decoded = (decoded << 4) | fmtscan_num(*in, 16) ; + if (c & 0x0040) + { + if (w >= max) return 0 ; + if (decoded == '/') lastslash = w ; + out[w++] = decoded ; + decoded = 0 ; + } + } + if (state > 0x13) return 0 ; + if (path) + { + size_t len = strlen(path) ; + if (len >= 3 && !memcmp(path + len - 3, "/..", 3)) return 0 ; + if (strstr(path, "/../")) return 0 ; + } + uri->host = host ; + uri->port = port ; + uri->path = path ? path : "/" ; + uri->query = query ; + uri->lastslash = path ? lastslash - (path - out) : 0 ; + uri->https = ssl ; + return w ; +} diff --git a/src/tipideed/cgi.c b/src/tipideed/cgi.c new file mode 100644 index 0000000..7a3ca03 --- /dev/null +++ b/src/tipideed/cgi.c @@ -0,0 +1,381 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "tipideed-internal.h" + +static void addenv_ (tipidee_rql const *rql, char const *k, char const *v, int slash) +{ + if (!stralloc_cats(&g.sa, k) + || !stralloc_catb(&g.sa, "=/", 1 + !!slash) + || !stralloc_cats(&g.sa, v) + || !stralloc_0(&g.sa)) + die500sys(rql, 111, "stralloc_catb") ; +} + +#define addenv(rql, k, v) addenv_(rql, k, (v), 0) +#define addenvslash(rql, k, v) addenv_(rql, k, (v), 1) + +static void delenv (tipidee_rql const *rql, char const *k) +{ + if (!stralloc_cats(&g.sa, k) + || !stralloc_0(&g.sa)) + die500sys(rql, 111, "stralloc_catb") ; +} + +static inline void modify_env (tipidee_rql const *rql, tipidee_headers const *hdr, size_t cl, char const *script, char const *infopath) +{ + uint32_t got = 0 ; + addenv(rql, "REQUEST_METHOD", tipidee_method_tostr(rql->m)) ; + if (cl) + { + char fmt[SIZE_FMT] ; + fmt[size_fmt(fmt, cl)] = 0 ; + addenv(rql, "CONTENT_LENGTH", fmt) ; + } + else delenv(rql, "CONTENT_LENGTH") ; + + if (infopath) addenvslash(rql, "PATH_INFO", infopath) ; + else delenv(rql, "PATH_INFO") ; + if (rql->uri.query) addenv(rql, "QUERY_STRING", rql->uri.query) ; + else delenv(rql, "QUERY_STRING") ; + addenv(rql, "SCRIPT_NAME", script) ; + + for (size_t i = 0 ; i < hdr->n ; i++) + { + char const *key = hdr->buf + hdr->list[i].left ; + char const *val = hdr->buf + hdr->list[i].right ; + if (!strcasecmp(key, "Authorization")) + { + size_t n = str_chr(val, ' ') ; + if (n) + { + char scheme[n] ; + memcpy(scheme, val, n-1) ; + scheme[n-1] = 0 ; + addenv(rql, "AUTH_TYPE", scheme) ; + got |= 1 ; + } + } + else if (!strcasecmp(key, "Content-Type")) { addenv(rql, "CONTENT_TYPE", val) ; got |= 2 ; } + else if (!strcasecmp(key, "Content-Length") || !strcasecmp(key, "Connection")) ; + else + { + size_t len = strlen(key), pos = g.sa.len + 5 ; + if (!stralloc_catb(&g.sa, "HTTP_", 5)) die500sys(rql, 111, "stralloc_catb") ; + addenv(rql, key, val) ; + for (char *s = g.sa.s + pos ; len-- ; s++) + if (*s == '-') *s = '_' ; + else if (*s >= 'a' && *s <= 'z') *s -= 32 ; + } + } + if (!(got & 1)) delenv(rql, "AUTH_TYPE") ; + if (!(got & 2)) delenv(rql, "CONTENT_TYPE") ; +} + +static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen) gccattr_noreturn ; +static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen) +{ + int p[2] ; + log_nph(argv, envp) ; + if (pipe(p) == -1) die500sys(rql, 111, "pipe") ; + if (bodylen) + { + switch (fork()) + { + case -1 : die500sys(rql, 111, "fork") ; + case 0 : + { + tain deadline ; + char buf[4096] ; + buffer b = BUFFER_INIT(&buffer_write, p[1], buf, 4096) ; + PROG = "tipidee (nph helper child)" ; + tain_add_g(&deadline, &g.cgitto) ; + close(p[0]) ; + if (ndelay_on(p[1]) == -1) strerr_diefu1sys(111, "set fd nonblocking") ; + if (buffer_timed_put_g(&b, body, bodylen, &deadline) < bodylen + || !buffer_timed_flush_g(&b, &deadline)) + strerr_diefu2sys(111, "write request body to nph ", argv[0]) ; + _exit(0) ; + } + default : break ; + } + } + close(p[1]) ; + if (fd_move(0, p[0]) == -1) die500sys(rql, 111, "fd_move") ; + exec_e(argv, envp) ; + die500sys(rql, errno == ENOENT ? 127 : 126, "exec nph ", argv[0]) ; +} + +static inline int run_cgi (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen, tipidee_headers *hdr, stralloc *sa) +{ + iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .events = IOPAUSE_WRITE } } ; + size_t bodyw = 0 ; + unsigned int rstate = 0 ; + tain deadline ; + pid_t pid ; + disize curheader = DISIZE_ZERO ; + uint32_t parserstate = 0 ; + buffer b ; + char buf[4096] ; + log_cgi(argv, envp) ; + { + int fd[2] = { 0, 1 } ; + pid = child_spawn2(argv[0], argv, envp, fd) ; + if (!pid) die500sys(rql, 111, "spawn ", argv[0]) ; + x[0].fd = fd[0] ; x[1].fd = fd[1] ; + } + if (!bodylen) + { + close(x[1].fd) ; + x[1].fd = -1 ; + LOLDEBUG("run_cgi: no request body, closing writing pipe to cgi") ; + } + buffer_init(&b, &buffer_read, x[0].fd, buf, 4096) ; + tain_add_g(&deadline, &g.cgitto) ; + while (x[0].fd >= 0) + { + int r = iopause_g(x, 1 + (x[1].fd >= 0), &deadline) ; + if (r == -1) die500sys(rql, 111, "iopause") ; + if (!r) + { + kill(pid, SIGTERM) ; + respond_504(rql) ; + break ; + } + if (x[1].fd >= 0 && x[1].revents & (IOPAUSE_WRITE | IOPAUSE_EXCEPT)) + { + size_t len = allwrite(x[1].fd, body + bodyw, bodylen - bodyw) ; + if (!len) + { + if (g.verbosity) strerr_warnwu2sys("write request body to cgi ", argv[0]) ; + bodyw = bodylen ; + } + else bodyw += len ; + if (bodyw >= bodylen) + { + close(x[1].fd) ; + x[1].fd = -1 ; + LOLDEBUG("run_cgi: finished writing body") ; + } + } + if (x[0].fd >= 0 && x[0].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT)) + { + switch (rstate) + { + case 0 : + { + r = tipidee_headers_parse_nb(&b, hdr, &curheader, &parserstate) ; + switch (r) + { + case -2 : break ; + case -1 : die500sys(rql, 111, "read from cgi ", argv[0]) ; + case 0 : + { + size_t n = buffer_len(&b) ; + if (!stralloc_readyplus(sa, n)) die500sys(rql, 111, "stralloc_readyplus") ; + buffer_getnofill(&b, sa->s + sa->len, n) ; + sa->len += n ; + 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 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") ; + } + if (!rstate) break ; + } + case 1 : + { + 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 die500sys(rql, 111, "read body", " from cgi ", argv[0]) ; + } + close(x[0].fd) ; + x[0].fd = -1 ; + rstate = 2 ; + LOLDEBUG("run_cgi: rstate = 2") ; + } + } + } + } + if (x[1].fd >= 0) close(x[1].fd) ; + if (x[0].fd >= 0) close(x[0].fd) ; + return rstate == 2 ; +} + +static inline int local_redirect (tipidee_rql *rql, char const *loc, char *uribuf, char const *cginame) +{ + size_t n ; + size_t hostlen = strlen(rql->uri.host) ; + uint16_t port = rql->uri.port ; + uint8_t ishttps = rql->uri.https ; + char hosttmp[hostlen + 1] ; + 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") ; + memcpy(uribuf + n, hosttmp, hostlen + 1) ; + rql->uri.host = uribuf + n ; + rql->uri.port = port ; + rql->uri.https = ishttps ; + return 1 ; +} + +static inline void print_cgi_headers (tipidee_headers const *hdr, size_t rbodylen) +{ + static char const *const nope_table[] = + { + "Connection", + "Date", + "Status", + "Content-Length", + 0 + } ; + for (size_t i = 0 ; i < hdr->n ; i++) + { + char const *key = hdr->buf + hdr->list[i].left ; + char const *const *p = nope_table ; + if (tipidee_response_header_builtin_search(key)) continue ; + if (str_start(key, "X-CGI-")) continue ; + for (; *p ; p++) if (!strcasecmp(key, *p)) break ; + if (*p) continue ; + buffer_putsnoflush(buffer_1, key) ; + buffer_putnoflush(buffer_1, ": ", 2) ; + buffer_putsnoflush(buffer_1, hdr->buf + hdr->list[i].right) ; + buffer_putnoflush(buffer_1, "\r\n", 2) ; + } + if (rbodylen) + { + char fmt[SIZE_FMT] ; + fmt[size_fmt(fmt, rbodylen)] = 0 ; + buffer_putsnoflush(buffer_1, "Content-Length: ") ; + buffer_putsnoflush(buffer_1, fmt) ; + buffer_putnoflush(buffer_1, "\r\n", 2) ; + } +} + +static inline int process_cgi_output (tipidee_rql *rql, tipidee_headers const *hdr, char const *rbody, size_t rbodylen, char *uribuf, char const *cginame) +{ + char const *location = tipidee_headers_search(hdr, "Location") ; + char const *x = tipidee_headers_search(hdr, "Status") ; + char const *reason_phrase = "OK" ; + unsigned int status = 0 ; + tain deadline ; + tain_add_g(&deadline, &g.writetto) ; + if (x) + { + size_t m = uint_scan(x, &status) ; + if (!m || x[m] != ' ') + die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " header") ; + reason_phrase = x + m + 1 ; + if (status >= 300 && status < 399 && !location) + die502x(rql, 1, "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") ; + } + if (location) + { + if (!location[0]) die502x(rql, 1, "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") ; + if (status < 300 || status > 399) + die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value", " for a client redirect response with document") ; + } + else + { + for (size_t i = 0 ; i < hdr->n ; i++) + { + char const *key = hdr->buf + hdr->list[i].left ; + if (!strcasecmp(key, "Location")) continue ; + if (str_start(key, "X-CGI-")) continue ; + die502x(rql, 1, "cgi ", cginame, "returned extra headers", " for a client redirect response without document") ; + } + status = 302 ; + reason_phrase = "Found" ; + } + } + else + { + if (!status) status = 200 ; + if (!tipidee_headers_search(hdr, "Content-Type")) + die502x(rql, 1, "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") ; + if (cln != rbodylen) + die502x(rql, 1, "cgi ", cginame, " returned a mismatching ", "Content-Length", " header") ; + } + + tipidee_response_status(buffer_1, rql, status, reason_phrase) ; + tipidee_response_header_common_put_g(buffer_1, !g.cont) ; + print_cgi_headers(hdr, rbodylen) ; + if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2) + strerr_diefu1sys(111, "write to stdout") ; + if (rbodylen) + { + if (buffer_timed_put_g(buffer_1, rbody, rbodylen, &deadline) < rbodylen) + strerr_diefu1sys(111, "write to stdout") ; + } + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} + +static inline int do_cgi (tipidee_rql *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen, char *uribuf) +{ + static stralloc sa = STRALLOC_ZERO ; + tipidee_headers hdr ; + char hdrbuf[2048] ; + sa.len = 0 ; + tipidee_headers_init(&hdr, hdrbuf, 2048) ; + if (!run_cgi(rql, argv, envp, body, bodylen, &hdr, &sa)) return 0 ; + return process_cgi_output(rql, &hdr, sa.s, sa.len, uribuf, argv[0]) ; +} + +int respond_cgi (tipidee_rql *rql, char const *fn, size_t docrootlen, char const *infopath, char *uribuf, tipidee_headers const *hdr, tipidee_resattr const *ra, char const *body, size_t bodylen) +{ + size_t sabase = g.sa.len ; + size_t envmax = g.envlen + 16 + TIPIDEE_HEADERS_MAX ; + char const *argv[2] = { fn, 0 } ; + char const *envp[envmax] ; + modify_env(rql, hdr, bodylen, fn + docrootlen, infopath) ; + env_merge(envp, envmax, (char const *const *)environ, g.envlen, g.sa.s + g.cwdlen + 1, g.sa.len - (g.cwdlen+1)) ; + g.sa.len = sabase ; + return ra->isnph ? do_nph(rql, argv, envp, body, bodylen) : + do_cgi(rql, argv, envp, body, bodylen, uribuf) ; +} diff --git a/src/tipideed/deps-exe/tipideed b/src/tipideed/deps-exe/tipideed new file mode 100644 index 0000000..aad1417 --- /dev/null +++ b/src/tipideed/deps-exe/tipideed @@ -0,0 +1,11 @@ +cgi.o +harden.o +log.o +options.o +regular.o +responses.o +send_file.o +tipideed.o +trace.o +libtipidee.a.xyzzy +-lskarnet diff --git a/src/tipideed/harden.c b/src/tipideed/harden.c new file mode 100644 index 0000000..5c925f2 --- /dev/null +++ b/src/tipideed/harden.c @@ -0,0 +1,50 @@ +/* ISC license. */ + +#include +#include + +#include +#include +#include + +#include +#include + +#include "tipideed-internal.h" + +static inline void tipideed_chroot (void) +{ +#ifdef SKALIBS_HASCHROOT + if (chroot(".") == -1) strerr_diefu1sys(111, "chroot") ; +#else + errno = ENOSYS ; + strerr_warnwu1sys("chroot") ; +#endif +} + +static inline void tipideed_dropuidgid (void) +{ + uid_t uid = 0 ; + gid_t gid = 0 ; + char const *gidfmt = getenv("GID") ; + char const *uidfmt = getenv("UID") ; + if (!uidfmt) strerr_dienotset(100, "UID") ; + if (!uid0_scan(uidfmt, &uid)) strerr_dieinvalid(100, "UID") ; + if (!gidfmt) strerr_dienotset(100, "GID") ; + if (!gid0_scan(gidfmt, &gid)) strerr_dieinvalid(100, "GID") ; + if (gid) + { +#ifdef SKALIBS_HASSETGROUPS + if (setgroups(1, &gid) == -1) strerr_diefu2sys(111, "setgroups to ", gidfmt) ; +#endif + if (setgid(gid) == -1) strerr_diefu2sys(111, "setgid to ", gidfmt) ; + } + if (uid) + if (setuid(uid) == -1) strerr_diefu2sys(111, "setuid to ", uidfmt) ; +} + +void tipideed_harden (unsigned int h) +{ + if (h & 2) tipideed_chroot() ; + if (h & 1) tipideed_dropuidgid() ; +} diff --git a/src/tipideed/log.c b/src/tipideed/log.c new file mode 100644 index 0000000..a257ff5 --- /dev/null +++ b/src/tipideed/log.c @@ -0,0 +1,59 @@ +/* ISC license. */ + +#include + +#include +#include +#include + +#include +#include "tipideed-internal.h" + +void log_start (void) +{ + if (g.verbosity >= 4) + strerr_warni7x("new connection", " from ip ", g.sa.s + g.remoteip, " (", g.sa.s + g.remotehost, ") port ", g.sa.s + g.remoteport) ; + else if (g.verbosity >= 3) + strerr_warni1x("new connection") ; +} + +void log_and_exit (int e) +{ + if (g.verbosity >= 3) + { + char fmt[INT_FMT] ; + fmt[int_fmt(fmt, e)] = 0 ; + strerr_warni2x("exiting ", fmt) ; + } + _exit(e) ; +} + +void log_request (tipidee_rql const *rql) +{ + if (g.verbosity >= 2) + { + char fmt[UINT16_FMT] ; + if (rql->uri.port) fmt[uint16_fmt(fmt, rql->uri.port)] = 0 ; + strerr_warnin(11, "request ", tipidee_method_tostr(rql->m), " for", rql->uri.host ? " host " : "", rql->uri.host ? rql->uri.host : "", rql->uri.port ? " port " : "", rql->uri.port ? fmt : "", " path ", rql->uri.path, rql->uri.query ? " query " : "", rql->uri.query ? rql->uri.query : "") ; + } +} + +void log_regular (char const *fn, char const *sizefmt, int ishead, char const *ct) +{ + if (g.verbosity >= 2) + strerr_warni8x("sending ", ishead ? "headers for " : "", "regular file ", fn, " (", sizefmt, " bytes) with type ", ct) ; +} + +void log_nph (char const *const *argv, char const *const *envp) +{ + if (g.verbosity >= 2) + strerr_warni3x("running ", "nph ", argv[0]) ; + (void)envp ; +} + +void log_cgi (char const *const *argv, char const *const *envp) +{ + if (g.verbosity >= 2) + strerr_warni3x("running ", "cgi ", argv[0]) ; + (void)envp ; +} diff --git a/src/tipideed/options.c b/src/tipideed/options.c new file mode 100644 index 0000000..d425943 --- /dev/null +++ b/src/tipideed/options.c @@ -0,0 +1,25 @@ +/* ISC license. */ + +#include + +#include +#include +#include +#include + +#include +#include "tipideed-internal.h" + +int respond_options (tipidee_rql const *rql, uint32_t flags) +{ + tain deadline ; + tipidee_response_status_line(buffer_1, rql, "200 OK") ; + tipidee_response_header_common_put_g(buffer_1, 0) ; + buffer_putsnoflush(buffer_1, "Content-Length: 0\r\nAllow: GET, HEAD") ; + if (flags & 1) buffer_putsnoflush(buffer_1, ", POST") ; + buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ; + tain_add_g(&deadline, &g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} diff --git a/src/tipideed/regular.c b/src/tipideed/regular.c new file mode 100644 index 0000000..1ac1095 --- /dev/null +++ b/src/tipideed/regular.c @@ -0,0 +1,48 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "tipideed-internal.h" + +int respond_regular (tipidee_rql const *rql, char const *fn, uint64_t size, tipidee_resattr const *ra) +{ + tain deadline ; + size_t n = tipidee_response_status_line(buffer_1, rql, "200 OK") ; + n += tipidee_response_header_common_put_g(buffer_1, !g.cont) ; + n += buffer_putsnoflush(buffer_1, "Content-Type: ") ; + n += buffer_putsnoflush(buffer_1, ra->content_type) ; + n += buffer_putsnoflush(buffer_1, "\r\nContent-Length: ") ; + { + char fmt[UINT64_FMT] ; + fmt[uint64_fmt(fmt, size)] = 0 ; + n += buffer_putsnoflush(buffer_1, fmt) ; + log_regular(fn, fmt, rql->m == TIPIDEE_METHOD_HEAD, ra->content_type) ; + } + n += buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ; + if (rql->m == TIPIDEE_METHOD_HEAD) + { + tain_add_g(&deadline, &g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; + } + else + { + int fd = open_read(fn) ; + if (fd == -1) + { + buffer_unput(buffer_1, n) ; + die500sys(rql, 111, "open ", fn) ; + } + send_file(fd, size, fn) ; + fd_close(fd) ; + } + return 0 ; +} diff --git a/src/tipideed/responses.c b/src/tipideed/responses.c new file mode 100644 index 0000000..02109b3 --- /dev/null +++ b/src/tipideed/responses.c @@ -0,0 +1,64 @@ +/* ISC license. */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "tipideed-internal.h" + +void response_error (tipidee_rql const *rql, char const *rsl, char const *text, int doclose) +{ + tain deadline ; + tipidee_response_error(buffer_1, rql, rsl, text, doclose || !g.cont) ; + tain_add_g(&deadline, &g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; +} + +void response_error_and_exit (tipidee_rql const *rql, char const *rsl, char const *text) +{ + response_error(rql, rsl, text, 1) ; + log_and_exit(0) ; +} + +void response_error_and_die (tipidee_rql const *rql, int e, char const *rsl, char const *text, char const *const *v, unsigned int n, int dosys) +{ + response_error(rql, rsl, text, 1) ; + if (dosys) strerr_dievsys(e, v, n) ; + else strerr_diev(e, v, n) ; +} + +void exit_405 (tipidee_rql const *rql, uint32_t options) +{ + tain deadline ; + tipidee_response_status_line(buffer_1, rql, "405 Method Not Allowed") ; + tipidee_response_header_common_put_g(buffer_1, 1) ; + buffer_putsnoflush(buffer_1, "Allow: GET, HEAD") ; + if (options & 1) buffer_putsnoflush(buffer_1, ", POST") ; + buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ; + tain_add_g(&deadline, &g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; + log_and_exit(0) ; +} + +void respond_30x (tipidee_rql const *rql, tipidee_redirection const *rd) +{ + static char const *rsl[4] = { "307 Temporary Redirect", "308 Permanent Redirect", "302 Found", "301 Moved Permanently" } ; + tain deadline ; + tipidee_response_status_line(buffer_1, rql, rsl[rd->type]) ; + tipidee_response_header_common_put_g(buffer_1, 0) ; + buffer_putsnoflush(buffer_1, "Location: ") ; + buffer_putsnoflush(buffer_1, rd->location) ; + if (rd->sub) buffer_putsnoflush(buffer_1, rd->sub) ; + buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ; + tain_add_g(&deadline, &g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; +} diff --git a/src/tipideed/send_file.c b/src/tipideed/send_file.c new file mode 100644 index 0000000..77b49dd --- /dev/null +++ b/src/tipideed/send_file.c @@ -0,0 +1,123 @@ +/* ISC license. */ + +#include + +#ifdef SKALIBS_HASSPLICE + +#include + +#include +#include +#include + +#include +#include +#include + +#include "tipideed-internal.h" + +void init_splice_pipe (void) +{ + if (pipenbcoe(g.p) == -1) + strerr_diefu1sys(111, "pipe2") ; +} + +struct spliceinfo_s +{ + ssize_t n ; + uint32_t last : 1 ; +} ; + +static int getfd (void *b) +{ + (void)b ; + return 1 ; +} + +static int isnonempty (void *b) +{ + struct spliceinfo_s *si = b ; + return !!si->n ; +} + +static int flush (void *b) +{ + struct spliceinfo_s *si = b ; + while (si->n) + { + ssize_t r = splice(g.p[0], 0, 1, 0, si->n, SPLICE_F_NONBLOCK | (si->last ? 0 : SPLICE_F_MORE)) ; + if (r == -1) return 0 ; + if (!r) return 1 ; + si->n -= r ; + } + return 1 ; +} + +void send_file (int fd, uint64_t n, char const *fn) +{ + tain deadline ; + struct spliceinfo_s si = { .last = 0 } ; + tain_add_g(&deadline, &g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu2sys(111, "write", " to stdout") ; + while (n) + { + si.n = splice(fd, 0, g.p[1], 0, n, 0) ; + if (si.n == -1) strerr_diefu2sys(111, "read from ", fn) ; + else if (!si.n) strerr_diefu3x(111, "serve ", fn, ": file was truncated") ; + else if (si.n > n) + { + si.n = n ; + if (g.verbosity >= 2) + strerr_warnw2x("serving elongated file: ", fn) ; + } + n -= si.n ; + if (!n) si.last = 1 ; + tain_add_g(&deadline, &g.writetto) ; + if (!timed_flush_g(&si, &getfd, &isnonempty, &flush, &deadline)) + strerr_diefu2sys(111, "splice", " to stdout") ; + } +} + +#else + +#include + +#include +#include +#include +#include + +#include "tipideed-internal.h" + +void init_splice_pipe (void) +{ +} + +void send_file (int fd, uint64_t n, char const *fn) +{ + tain deadline ; + struct iovec v[2] ; + while (n) + { + ssize_t r ; + buffer_rpeek(buffer_1, v) ; + r = allreadv(fd, v, 2) ; + if (r > n) + if (r == -1) strerr_diefu2sys(111, "read from ", fn) ; + if (!r) strerr_diefu3x(111, "serve ", fn, ": file was truncated") ; + if (r > n) + { + r = n ; + if (g.verbosity >= 2) + strerr_warnw2x("serving elongated file: ", fn) + } + buffer_rseek(b, r) ; + tain_add_g(&deadline, g.writetto) ; + if (!buffer_timed_flush_g(buffer_1, &deadline)) + strerr_diefu1sys(111, "write to stdout") ; + n -= r ; + } +} + +#endif diff --git a/src/tipideed/tipideed-internal.h b/src/tipideed/tipideed-internal.h new file mode 100644 index 0000000..c4ff928 --- /dev/null +++ b/src/tipideed/tipideed-internal.h @@ -0,0 +1,147 @@ +/* ISC license. */ + +#ifndef TIPIDEED_INTERNAL_H +#define TIPIDEED_INTERNAL_H + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define URI_BUFSIZE 4096 +#define HDR_BUFSIZE 8192 + +typedef struct tipidee_resattr_s tipidee_resattr, *tipidee_resattr_ref ; +struct tipidee_resattr_s +{ + char const *content_type ; + uint32_t iscgi : 1 ; + uint32_t isnph : 1 ; +} ; +#define TIPIDEE_RESATTR_ZERO { .content_type = 0, .iscgi = 0, .isnph = 0 } + +struct global_s +{ + tipidee_conf conf ; + stralloc sa ; + size_t envlen ; + size_t localip ; + size_t localhost ; + size_t localport ; + size_t localportlen ; + size_t remoteip ; + size_t remotehost ; + size_t remoteport ; + size_t cwdlen ; + size_t indexlen ; + tain readtto ; + tain writetto ; + tain cgitto ; + char const *indexnames[16] ; + int p[2] ; + uint32_t maxrqbody ; + uint32_t maxcgibody ; + uint16_t indexn : 4 ; + uint16_t verbosity : 3 ; + uint16_t cont : 2 ; +} ; +#define GLOBAL_ZERO \ +{ \ + .conf = TIPIDEE_CONF_ZERO, \ + .sa = STRALLOC_ZERO, \ + .envlen = 0, \ + .localip = 0, \ + .localhost = 0, \ + .localport = 0, \ + .localportlen = 0, \ + .remoteip = 0, \ + .remotehost = 0, \ + .remoteport = 0, \ + .cwdlen = 1, \ + .indexlen = 0, \ + .readtto = TAIN_ZERO, \ + .writetto = TAIN_ZERO, \ + .cgitto = TAIN_ZERO, \ + .indexnames = { 0 }, \ + .p = { -1, -1 }, \ + .maxrqbody = 0, \ + .maxcgibody = 0, \ + .indexn = 0, \ + .verbosity = 1, \ + .cont = 1 \ +} + +extern struct global_s g ; + + + /* uid/gid and chroot */ + +extern void tipideed_harden (unsigned int) ; + + + /* Responses */ + +extern void response_error (tipidee_rql const *, char const *, char const *, int) ; +extern void response_error_and_exit (tipidee_rql const *, char const *, char const *) gccattr_noreturn ; +extern void response_error_and_die (tipidee_rql const *, int e, char const *, char const *, char const *const *, unsigned int, int) gccattr_noreturn ; + +#define exit_400(r, s) response_error_and_exit(r, "400 Bad Request", s) +extern void exit_405 (tipidee_rql const *, uint32_t) gccattr_noreturn ; +#define exit_408(r) response_error_and_exit(r, "408 Request Timeout", "") +#define exit_413(r, s) response_error_and_exit(r, "413 Request Entity Too Large", s) +#define exit_501(r, s) response_error_and_exit(r, "501 Not Implemented", s) + +#define respond_403(r) response_error(r, "403 Forbidden", "Missing credentials to access the URI.", 0) +#define respond_404(r) response_error(r, "404 Not Found", "The request URI was not found.", 0) +#define respond_414(r) response_error(r, "414 URI Too Long", "The request URI had an oversized component.", 0) +extern void respond_30x (tipidee_rql const *, tipidee_redirection const *) ; +#define respond_504(r) response_error(r, "504 Gateway Timeout", "The CGI script took too long to answer.", 0) + +#define diefx(r, e, rsl, text, ...) response_error_and_die(r, e, rsl, text, strerr_array(PROG, ": fatal: ", __VA_ARGS__), sizeof(strerr_array(__VA_ARGS__))/sizeof(char const *)+2, 0) +#define diefusys(r, e, rsl, text, ...) response_error_and_die(r, e, rsl, text, strerr_array(PROG, ": fatal: ", "unable to ", __VA_ARGS__), sizeof(strerr_array(__VA_ARGS__))/sizeof(char const *)+3, 1) +#define die500x(r, e, ...) diefx(r, e, "500 Internal Server Error", "Bad server configuration.", __VA_ARGS__) +#define die500sys(r, e, ...) diefusys(r, e, "500 Internal Server Error", "System error.", __VA_ARGS__) +#define die502x(r, e, ...) diefx(r, e, "502 Bad Gateway", "Bad CGI script.", __VA_ARGS__) + + /* Trace */ + +extern int respond_trace (char const *, tipidee_rql const *, tipidee_headers const *) ; + + + /* Options */ + +extern int respond_options (tipidee_rql const *, uint32_t) ; + + + /* send_file */ + +extern void init_splice_pipe (void) ; +extern void send_file (int, uint64_t, char const *) ; + + + /* regular */ + +extern int respond_regular (tipidee_rql const *, char const *, uint64_t, tipidee_resattr const *) ; + + + /* cgi */ + +extern int respond_cgi (tipidee_rql *, char const *, size_t, char const *, char *, tipidee_headers const *, tipidee_resattr const *, char const *, size_t) ; + + + /* log */ + +extern void log_start (void) ; +extern void log_and_exit (int) gccattr_noreturn ; +extern void log_request (tipidee_rql const *) ; +extern void log_regular (char const *, char const *, int, char const *) ; +extern void log_nph (char const *const *, char const *const *) ; +extern void log_cgi (char const *const *, char const *const *) ; + +#endif diff --git a/src/tipideed/tipideed.c b/src/tipideed/tipideed.c new file mode 100644 index 0000000..42e65a6 --- /dev/null +++ b/src/tipideed/tipideed.c @@ -0,0 +1,514 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "tipideed-internal.h" + +#define USAGE "tipideed [ -v verbosity ] [ -f conffile ] [ -R chroot ] [ -U ]" +#define dieusage() strerr_dieusage(100, USAGE) +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") + +#define ARGV_MAX 128 + +struct global_s g = GLOBAL_ZERO ; + +static void sigchld_handler (int sig) +{ + (void)sig ; + wait_reap() ; +} + +static inline void prep_env (void) +{ + static char const basevars[] = "PROTO\0GATEWAY_INTERFACE=CGI/1.1\0SERVER_PROTOCOL=HTTP/1.1\0SERVER_SOFTWARE=tipidee/" TIPIDEE_VERSION ; + static char const sslvars[] = "SSL_PROTOCOL\0SSL_CIPHER\0SSL_TLS_SNI_SERVERNAME\0SSL_PEER_CERT_HASH\0SSL_PEER_CERT_SUBJECT\0HTTPS=on" ; + char const *x = getenv("SSL_PROTOCOL") ; + if (!stralloc_readyplus(&g.sa, 320)) dienomem() ; + if (sagetcwd(&g.sa) == -1) strerr_diefu1sys(111, "getcwd") ; + if (g.sa.len == 1) g.sa.len = 0 ; + g.cwdlen = g.sa.len ; + if (g.cwdlen && !stralloc_0(&g.sa)) dienomem() ; + if (!stralloc_catb(&g.sa, basevars, sizeof(basevars))) dienomem() ; + if (x && !stralloc_catb(&g.sa, sslvars, sizeof(sslvars))) dienomem() ; + x = getenv(basevars) ; + if (!x) strerr_dienotset(100, "PROTO") ; + { + size_t protolen = strlen(x) ; + size_t m ; + ip46 ip ; + uint16_t port ; + char fmt[IP46_FMT] ; + char var[protolen + 11] ; + memcpy(var, x, protolen) ; + + memcpy(var + protolen, "LOCALIP", 8) ; + x = getenv(var) ; + if (!x) strerr_dienotset(100, var) ; + if (!ip46_scan(x, &ip)) strerr_dieinvalid(100, var) ; + if (!stralloc_catb(&g.sa, var, protolen + 8) + || !stralloc_catb(&g.sa, "SERVER_ADDR=", 12)) dienomem() ; + g.localip = g.sa.len ; + m = ip46_fmt(fmt, &ip) ; fmt[m++] = 0 ; + if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ; + + memcpy(var + protolen, "LOCALHOST", 10) ; + x = getenv(var) ; + if (!x) strerr_dienotset(100, var) ; + if (!stralloc_catb(&g.sa, var, protolen + 10) + || !stralloc_catb(&g.sa, "SERVER_NAME=", 12)) dienomem() ; + g.localhost = g.sa.len ; + if (!stralloc_cats(&g.sa, x) || !stralloc_0(&g.sa)) dienomem() ; + + memcpy(var + protolen, "LOCALPORT", 10) ; + x = getenv(var) ; + if (!x) strerr_dienotset(100, var) ; + if (!uint160_scan(x, &port)) strerr_dieinvalid(100, var) ; + if (!stralloc_catb(&g.sa, var, protolen + 10) + || !stralloc_catb(&g.sa, "SERVER_PORT=", 12)) dienomem() ; + g.localport = g.sa.len ; + g.localportlen = uint16_fmt(fmt, port) ; fmt[g.localportlen] = 0 ; + if (!stralloc_catb(&g.sa, fmt, g.localportlen + 1)) dienomem() ; + + memcpy(var + protolen, "REMOTEIP", 9) ; + x = getenv(var) ; + if (!x) strerr_dienotset(100, var) ; + if (!ip46_scan(x, &ip)) strerr_dieinvalid(100, var) ; + if (!stralloc_catb(&g.sa, var, protolen + 9) + || !stralloc_catb(&g.sa, "REMOTE_ADDR=", 12)) dienomem() ; + g.remoteip = g.sa.len ; + m = ip46_fmt(fmt, &ip) ; fmt[m++] = 0 ; + if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ; + + memcpy(var + protolen, "REMOTEHOST", 11) ; + x = getenv(var) ; + if ((x && !stralloc_catb(&g.sa, var, protolen + 11)) + || !stralloc_catb(&g.sa, "REMOTE_HOST=", 12)) dienomem() ; + g.remotehost = g.sa.len ; + if (!stralloc_cats(&g.sa, x ? x : fmt) + || !stralloc_0(&g.sa)) dienomem() ; + + memcpy(var + protolen, "REMOTEPORT", 11) ; + x = getenv(var) ; + if (!x) strerr_dienotset(100, var) ; + if (!uint160_scan(x, &port)) strerr_dieinvalid(100, var) ; + if (!stralloc_catb(&g.sa, var, protolen + 11) + || !stralloc_catb(&g.sa, "REMOTE_PORT=", 12)) dienomem() ; + g.remoteport = g.sa.len ; + m = uint16_fmt(fmt, port) ; fmt[m++] = 0 ; + if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ; + + memcpy(var + protolen, "REMOTEINFO", 11) ; + x = getenv(var) ; + if (x) + if (!stralloc_catb(&g.sa, var, protolen + 11) + || !stralloc_catb(&g.sa, "REMOTE_IDENT=", 13) + || !stralloc_cats(&g.sa, x) || !stralloc_0(&g.sa)) dienomem() ; + } +} + +static uint32_t get_uint32 (char const *key) +{ + uint32_t n ; + if (!tipidee_conf_get_uint32(&g.conf, key, &n)) + strerr_diefu2sys(100, "read config value for ", key) ; + return n ; +} + +static inline unsigned int indexify (tipidee_rql const *rql, char *s, struct stat *st) +{ + size_t len = strlen(s) ; + unsigned int i = 0 ; + if (s[len - 1] != '/') s[len++] = '/' ; + for (; i < g.indexn ; i++) + { + strcpy(s + len, g.indexnames[i]) ; + if (stat(s, st) == 0) break ; + switch (errno) + { + case EACCES : return 403 ; + case ENAMETOOLONG : return 414 ; + case ENOTDIR : return 404 ; + case ENOENT : continue ; + default : die500sys(rql, 111, "stat ", s) ; + } + } + if (i >= g.indexn) return 404 ; + if (S_ISDIR(st->st_mode)) die500x(rql, 103, "bad document hierarchy: ", s, " is a directory") ; + return 0 ; +} + +static inline void get_resattr (tipidee_rql const *rql, char const *res, tipidee_resattr *ra) +{ + static stralloc sa = STRALLOC_ZERO ; + sa.len = 0 ; + if (sarealpath(&sa, res) == -1 || !stralloc_0(&sa)) die500sys(rql, 111, "realpath ", res) ; + if (strncmp(sa.s, g.sa.s, g.cwdlen) || sa.s[g.cwdlen] != '/') + die500x(rql, 102, "resource ", res, " points outside of the server's root") ; + + { + char const *attr = 0 ; + size_t len = sa.len - g.cwdlen + 1 ; + char key[len + 1] ; + key[0] = 'A' ; key[1] = ':' ; + memcpy(key + 2, sa.s + 1 + g.cwdlen, sa.len - 1 - g.cwdlen) ; + key[len] = '/' ; + errno = ENOENT ; + while (!attr) + { + if (errno != ENOENT) die500x(rql, 102, "invalid configuration data for ", key) ; + while (len > 2 && key[len] != '/') len-- ; + if (len <= 2) break ; + key[len--] = 0 ; + attr = tipidee_conf_get_string(&g.conf, key) ; + key[0] = 'a' ; + } + if (attr) + { + if (*attr < '@' || *attr > 'G') die500x(rql, 102, "invalid configuration data for ", key) ; + ra->iscgi = *attr & ~'@' & 1 ; + if (attr[1]) ra->content_type = attr + 1 ; + if (ra->iscgi) + { + char const *nphprefix ; + char *p ; + key[0] = 'N' ; + p = strchr(key+2, '/') ; + if (p) *p = 0 ; + nphprefix = tipidee_conf_get_string(&g.conf, key) ; + if (nphprefix) + { + char const *base = strrchr(sa.s + g.cwdlen, '/') ; + if (str_start(base + 1, nphprefix)) ra->isnph = 1 ; + } + } + } + } + + if (!ra->iscgi && !ra->content_type) + { + ra->content_type = tipidee_conf_get_content_type(&g.conf, sa.s + g.cwdlen) ; + if (!ra->content_type) die500sys(rql, 111, "get content type for ", sa.s + g.cwdlen) ; + } +} + +static inline int serve (tipidee_rql *rql, char const *docroot, size_t docrootlen, char *uribuf, tipidee_headers const *hdr, char const *body, size_t bodylen) +{ + tipidee_resattr ra = TIPIDEE_RESATTR_ZERO ; + size_t pathlen = strlen(rql->uri.path) ; + char const *infopath = 0 ; + struct stat st ; + char fn[docrootlen + pathlen + 2 + g.indexlen] ; + memcpy(fn, docroot, docrootlen) ; + memcpy(fn + docrootlen, rql->uri.path, pathlen) ; + fn[docrootlen + pathlen] = 0 ; + + /* Redirection */ + + if (rql->m != TIPIDEE_METHOD_OPTIONS) + { + tipidee_redirection rd = TIPIDEE_REDIRECTION_ZERO ; + int e = tipidee_conf_get_redirection(&g.conf, fn, docrootlen, &rd) ; + if (e == -1) die500sys(rql, 111, "get redirection data for ", fn) ; + if (e) + { + respond_30x(rql, &rd) ; + return 0 ; + } + } + + /* Resource in the filesystem */ + + if (stat(fn, &st) == -1) + { + size_t pos = docrootlen + pathlen - 1 ; + for (;;) + { + while (fn[pos] != '/') pos-- ; + if (pos <= docrootlen) { respond_404(rql) ; return 0 ; } + fn[pos] = 0 ; + if (stat(fn, &st) == 0) break ; + switch (errno) + { + case ENOTDIR : + case ENOENT : fn[pos--] = '/' ; break ; + case EACCES : respond_403(rql) ; return 0 ; + case ENAMETOOLONG : respond_414(rql) ; return 0 ; + default : die500sys(rql, 111, "stat ", fn) ; + } + } + infopath = fn + pos + 1 ; + } + if (S_ISDIR(st.st_mode)) + { + if (infopath) { respond_404(rql) ; return 0 ; } + switch (indexify(rql, fn, &st)) + { + case 403 : respond_403(rql) ; return 0 ; + case 404 : respond_404(rql) ; return 0 ; + case 414 : respond_414(rql) ; return 0 ; + case 0 : break ; + } + } + LOLDEBUG("serve: %s with %s %s, docroot %s", fn, infopath ? "infopath" : "no", infopath ? infopath : "infopath", docroot) ; + + get_resattr(rql, fn, &ra) ; + + if (!ra.iscgi) + { + if (infopath) { respond_404(rql) ; return 0 ; } + if (rql->m == TIPIDEE_METHOD_POST) exit_405(rql, 0) ; + } + + if (rql->m == TIPIDEE_METHOD_OPTIONS) + return respond_options(rql, ra.iscgi) ; + else if (ra.iscgi) + return respond_cgi(rql, fn, docrootlen, infopath, uribuf, hdr, &ra, body, bodylen) ; + else + return respond_regular(rql, fn, st.st_size, &ra) ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + stralloc bodysa = STRALLOC_ZERO ; + char progstr[14 + PID_FMT] = "tipideed: pid " ; + progstr[14 + pid_fmt(progstr + 14, getpid())] = 0 ; + PROG = progstr ; + + { + char const *conffile = "/etc/tipidee.conf.cdb" ; + char const *newroot = 0 ; + unsigned int h = 0 ; + int gotv = 0 ; + subgetopt l = SUBGETOPT_ZERO ; + + for (;;) + { + int opt = subgetopt_r(argc, argv, "v:f:d:RU", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'v' : + { + unsigned int n ; + if (!uint0_scan(l.arg, &n)) dieusage() ; + if (n > 7) n = 7 ; + g.verbosity = n ; + gotv = 1 ; + break ; + } + case 'f' : conffile = l.arg ; break ; + case 'd' : newroot = l.arg ; break ; + case 'R' : h |= 3 ; break ; + case 'U' : h |= 1 ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + + g.envlen = env_len(envp) ; + if (!tipidee_conf_init(&g.conf, conffile)) + strerr_diefu2sys(111, "find configuration in ", conffile) ; + if (newroot && chdir(newroot) == -1) + strerr_diefu2sys(111, "chdir to ", newroot) ; + tipideed_harden(h) ; + if (!gotv) g.verbosity = get_uint32("G:verbosity") ; + } + + prep_env() ; + tain_from_millisecs(&g.readtto, get_uint32("G:read_timeout")) ; + tain_from_millisecs(&g.writetto, get_uint32("G:write_timeout")) ; + tain_from_millisecs(&g.cgitto, get_uint32("G:cgi_timeout")) ; + g.maxrqbody = get_uint32("G:max_request_body_length") ; + g.maxcgibody = get_uint32("G:max_cgi_body_length") ; + { + unsigned int n = tipidee_conf_get_argv(&g.conf, "G:index_file", g.indexnames, 16, &g.indexlen) ; + if (!n) strerr_dief3x(100, "bad", " config value for ", "G:index_file") ; + g.indexn = n-1 ; + } + + if (ndelay_on(0) == -1 || ndelay_on(1) == -1) + strerr_diefu1sys(111, "set I/O nonblocking") ; + init_splice_pipe() ; + if (!sig_catch(SIGCHLD, &sigchld_handler)) + strerr_diefu1sys(111, "set SIGCHLD handler") ; + if (!tain_now_set_stopwatch_g()) + strerr_diefu1sys(111, "initialize clock") ; + + log_start() ; + + + /* Main loop */ + + while (g.cont) + { + tain deadline ; + tipidee_rql rql = TIPIDEE_RQL_ZERO ; + tipidee_headers hdr ; + int e ; + char const *x ; + size_t content_length ; + tipidee_transfercoding tcoding = TIPIDEE_TRANSFERCODING_UNKNOWN ; + char uribuf[URI_BUFSIZE] ; + char hdrbuf[HDR_BUFSIZE] ; + + tain_add_g(&deadline, &g.readtto) ; + bodysa.len = 0 ; + + e = tipidee_rql_read_g(buffer_0, uribuf, URI_BUFSIZE, &content_length, &rql, &deadline) ; + switch (e) + { + case -1 : log_and_exit(1) ; /* Timeout, malicious client, or shitty client */ + case 0 : break ; + case 400 : exit_400(&rql, "Syntax error in request line") ; + default : strerr_dief2x(101, "can't happen: ", "unknown tipidee_rql_read return code") ; + } + if (rql.http_major != 1) log_and_exit(1) ; + if (rql.http_minor > 2) exit_400(&rql, "Bad HTTP version") ; + + content_length = 0 ; + tipidee_headers_init(&hdr, hdrbuf, HDR_BUFSIZE) ; + e = tipidee_headers_timed_parse_g(buffer_0, &hdr, &deadline) ; + switch (e) + { + case -1 : log_and_exit(1) ; /* connection issue, client timeout, etc. */ + case 0 : break ; + case 400 : exit_400(&rql, "Syntax error in headers") ; + case 408 : exit_408(&rql) ; /* timeout */ + case 413 : exit_413(&rql, hdr.n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data") ; + case 500 : die500x(&rql, 101, "can't happen: ", "avltreen_insert failed") ; + default : die500x(&rql, 101, "can't happen: ", "unknown tipidee_headers_parse return code") ; + } + + if (rql.http_minor == 0) g.cont = 0 ; + else + { + x = tipidee_headers_search(&hdr, "Connection") ; + if (x) + { + if (strstr(x, "close")) g.cont = 0 ; + else if (strstr(x, "keep-alive")) g.cont = 2 ; + } + } + + x = tipidee_headers_search(&hdr, "Transfer-Encoding") ; + if (x) + { + if (strcmp(x, "chunked")) exit_400(&rql, "unsupported Transfer-Encoding") ; + else tcoding = TIPIDEE_TRANSFERCODING_CHUNKED ; + } + else + { + x = tipidee_headers_search(&hdr, "Content-Length") ; + if (x) + { + if (!size_scan(x, &content_length)) exit_400(&rql, "Invalid Content-Length") ; + else if (content_length) tcoding = TIPIDEE_TRANSFERCODING_FIXED ; + else tcoding = TIPIDEE_TRANSFERCODING_NONE ; + } + else tcoding = TIPIDEE_TRANSFERCODING_NONE ; + } + + if (tcoding != TIPIDEE_TRANSFERCODING_NONE && rql.m != TIPIDEE_METHOD_POST) + exit_400(&rql, "only POST requests can have an entity body") ; + + switch (rql.m) + { + case TIPIDEE_METHOD_GET : + case TIPIDEE_METHOD_HEAD : + case TIPIDEE_METHOD_POST : break ; + case TIPIDEE_METHOD_OPTIONS : + if (!rql.uri.path) { respond_options(&rql, 1) ; continue ; } + break ; + case TIPIDEE_METHOD_PUT : + case TIPIDEE_METHOD_DELETE : exit_405(&rql, 1) ; + case TIPIDEE_METHOD_TRACE : respond_trace(hdrbuf, &rql, &hdr) ; continue ; + case TIPIDEE_METHOD_CONNECT : exit_501(&rql, "CONNECT method unsupported") ; + case TIPIDEE_METHOD_PRI : exit_501(&rql, "PRI method attempted with HTTP/1.1") ; + default : die500x(&rql, 101, "can't happen: unknown HTTP method") ; + } + + if (!rql.uri.host) + { + x = tipidee_headers_search(&hdr, "Host") ; + if (x) + { + char *p = strchr(x, ':') ; + if (p) + { + if (!uint160_scan(p+1, &rql.uri.port)) exit_400(&rql, "Invalid Host header") ; + *p = 0 ; + } + if (!*x || *x == '.') exit_400(&rql, "Invalid Host header") ; + rql.uri.host = x ; + } + else if (!rql.http_minor) rql.uri.host = "@" ; + else exit_400(&rql, "Missing Host header") ; + } + + { + size_t hostlen = strlen(rql.uri.host) ; + char docroot[hostlen + g.localportlen + 2] ; + if (rql.uri.host[hostlen - 1] == '.') hostlen-- ; + memcpy(docroot, rql.uri.host, hostlen) ; + docroot[hostlen] = ':' ; + memcpy(docroot + hostlen + 1, g.sa.s + g.localport, g.localportlen + 1) ; + + /* All good. Read the body if any */ + + switch (tcoding) + { + case TIPIDEE_TRANSFERCODING_FIXED : + { + if (content_length > g.maxrqbody) exit_413(&rql, "Request body too large") ; + if (!stralloc_ready(&bodysa, content_length)) die500sys(&rql, 111, "stralloc_ready") ; + if (buffer_timed_get_g(buffer_0, bodysa.s, content_length, &deadline) < content_length) + { + if (errno == ETIMEDOUT) exit_408(&rql) ; + else exit_400(&rql, "Request body does not match Content-Length") ; + } + bodysa.len = content_length ; + } + case TIPIDEE_TRANSFERCODING_CHUNKED : + { + if (!tipidee_chunked_read_g(buffer_0, &bodysa, g.maxrqbody, &deadline)) + { + if (error_temp(errno)) die500sys(&rql, 111, "decode chunked body") ; + else if (errno == EMSGSIZE) exit_413(&rql, "Request body too large") ; + else exit_400(&rql, "Invalid chunked body") ; + } + } + default : break ; + } + + log_request(&rql) ; + + + /* And serve the resource. The loop is in case of CGI local-redirection. */ + + while (serve(&rql, docroot, hostlen + 1 + g.localportlen, uribuf, &hdr, bodysa.s, bodysa.len)) ; + } + } + log_and_exit(0) ; +} diff --git a/src/tipideed/trace.c b/src/tipideed/trace.c new file mode 100644 index 0000000..4761ea5 --- /dev/null +++ b/src/tipideed/trace.c @@ -0,0 +1,67 @@ +/* ISC license. */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "tipideed-internal.h" + +int respond_trace (char const *buf, tipidee_rql const *rql, tipidee_headers const *hdr) +{ + tain deadline ; + size_t cl = 0 ; + char fmt[SIZE_FMT] ; + tipidee_response_status_line(buffer_1, rql, "200 OK") ; + tipidee_response_header_common_put_g(buffer_1, 0) ; + buffer_putsnoflush(buffer_1, "Content-Type: message/http\r\nContent-Length: ") ; + cl += strlen(tipidee_method_tostr(rql->m)) + 1; + if (rql->uri.host) cl += 7 + rql->uri.https + strlen(rql->uri.host) ; + cl += strlen(rql->uri.path) + (rql->uri.query ? 1 + strlen(rql->uri.query) : 0) ; + cl += 6 + uint_fmt(0, rql->http_major) + 1 + uint_fmt(0, rql->http_minor) + 2 ; + for (size_t i = 0 ; i < hdr->n ; i++) + cl += strlen(buf + hdr->list[i].left) + 2 + strlen(buf + hdr->list[i].right) + 2 ; + cl += 2 ; + buffer_putnoflush(buffer_1, fmt, size_fmt(fmt, cl)) ; + buffer_putsnoflush(buffer_1, "\r\n\r\n") ; + + buffer_putsnoflush(buffer_1, tipidee_method_tostr(rql->m)) ; + buffer_putnoflush(buffer_1, " ", 1) ; + if (rql->uri.host) + { + buffer_putsnoflush(buffer_1, rql->uri.https ? "https://" : "http://") ; + buffer_putsnoflush(buffer_1, rql->uri.host) ; + } + buffer_putsnoflush(buffer_1, rql->uri.path) ; + if (rql->uri.query) + { + buffer_putnoflush(buffer_1, "?", 1) ; + buffer_putsnoflush(buffer_1, rql->uri.query) ; + } + buffer_putsnoflush(buffer_1, " HTTP/") ; + buffer_putnoflush(buffer_1, fmt, uint_fmt(fmt, rql->http_major)) ; + buffer_putnoflush(buffer_1, ".", 1) ; + buffer_putnoflush(buffer_1, fmt, uint_fmt(fmt, rql->http_minor)) ; + buffer_putsnoflush(buffer_1, "\r\n") ; + for (size_t i = 0 ; i < hdr->n ; i++) + { + size_t len = strlen(buf + hdr->list[i].left) ; + tain_add_g(&deadline, &g.writetto) ; + if (buffer_timed_put_g(buffer_1, buf + hdr->list[i].left, len, &deadline) < len) goto err ; + if (buffer_timed_put_g(buffer_1, ": ", 2, &deadline) < 2) goto err ; + len = strlen(buf + hdr->list[i].right) ; + if (buffer_timed_put_g(buffer_1, buf + hdr->list[i].right, len, &deadline) < len) goto err ; + if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2) goto err ; + } + if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2 + || !buffer_timed_flush_g(buffer_1, &deadline)) goto err ; + return 0 ; + + err: + strerr_diefu1sys(111, "write to stdout") ; +} diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh new file mode 100755 index 0000000..befe021 --- /dev/null +++ b/tools/gen-deps.sh @@ -0,0 +1,100 @@ +#!/bin/sh -e + +. package/info + +echo '#' +echo '# This file has been generated by tools/gen-deps.sh' +echo '#' +echo + +internal_libs= + +for dir in src/include/${package} src/* ; do + for file in $(ls -1 $dir | grep -- \\.h$) ; do + { + grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ; + grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2 + } | sort -u | { + deps= + while read dep ; do + if echo $dep | grep -q "^${package}/" ; then + deps="$deps src/include/$dep" + elif test -f "${dir}/$dep" ; then + deps="$deps ${dir}/$dep" + else + deps="$deps src/include-local/$dep" + fi + done + if test -n "$deps" ; then + echo "${dir}/${file}:${deps}" + fi + } + done +done + +for dir in src/* ; do + for file in $(ls -1 $dir | grep -- \\.c$) ; do + { + grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ; + grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2 + } | sort -u | { + deps=" ${dir}/$file" + while read dep ; do + if echo $dep | grep -q "^${package}/" ; then + deps="$deps src/include/$dep" + elif test -f "${dir}/$dep" ; then + deps="$deps ${dir}/$dep" + else + deps="$deps src/include-local/$dep" + fi + done + o=$(echo $file | sed s/\\.c$/.o/) + lo=$(echo $file | sed s/\\.c$/.lo/) + echo "${dir}/${o} ${dir}/${lo}:${deps}" + } + done +done +echo + +for dir in $(ls -1 src | grep -v ^include) ; do + for file in $(ls -1 src/$dir/deps-lib) ; do + deps= + libs= + while read dep ; do + if echo $dep | grep -q -e ^-l -e '^\${.*_LIB}' ; then + libs="$libs $dep" + else + deps="$deps src/$dir/$dep" + fi + done < src/$dir/deps-lib/$file + echo 'ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)' + echo "lib${file}.a.xyzzy:$deps" + echo else + echo "lib${file}.a.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')" + echo endif + if grep -E "^LIB_DEFS [+:]=" package/targets.mak | grep -qF "$file" ; then + echo "lib${file}.so.xyzzy: EXTRA_LIBS :=$libs" + echo "lib${file}.so.xyzzy:$(echo "$deps" | sed 's/\.o/.lo/g')" + else + internal_libs="$internal_libs lib${file}.a.xyzzy" + fi + done + + for file in $(ls -1 src/$dir/deps-exe) ; do + deps= + libs= + while read dep ; do + if echo $dep | grep -q -- \\.o$ ; then + dep="src/$dir/$dep" + fi + if echo $dep | grep -q -e ^-l -e '^\${.*_LIB}' ; then + libs="$libs $dep" + else + deps="$deps $dep" + fi + done < src/$dir/deps-exe/$file + echo "$file: EXTRA_LIBS :=$libs" + echo "$file: src/$dir/$file.o$deps" + done +done +echo "INTERNAL_LIBS :=$internal_libs" diff --git a/tools/install.sh b/tools/install.sh new file mode 100755 index 0000000..e96dd7b --- /dev/null +++ b/tools/install.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +usage() { + echo "usage: $0 [ -D ] [ -l ] [ -m mode ] [ -O owner:group ] src dst" 1>&2 + exit 1 +} + +mkdirp=false +symlink=false +mode=0755 +og= + +while getopts Dlm:O: name ; do + case "$name" in + D) mkdirp=true ;; + l) symlink=true ;; + m) mode=$OPTARG ;; + O) og=$OPTARG ;; + ?) usage ;; + esac +done +shift $(($OPTIND - 1)) + +test "$#" -eq 2 || usage +src=$1 +dst=$2 +tmp="$dst.tmp.$$" + +case "$dst" in + */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;; +esac + +set -C +set -e + +if $mkdirp ; then + umask 022 + case "$2" in + */*) mkdir -p "${dst%/*}" ;; + esac +fi + +trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP + +umask 077 + +if $symlink ; then + ln -s "$src" "$tmp" +else + cat < "$1" > "$tmp" + if test -n "$og" ; then + chown -- "$og" "$tmp" + fi + chmod -- "$mode" "$tmp" +fi + +mv -f "$tmp" "$dst" +if test -d "$dst" ; then + rm -f "$dst/$(basename $tmp)" + if $symlink ; then + mkdir "$tmp" + ln -s "$src" "$tmp/$(basename $dst)" + mv -f "$tmp/$(basename $dst)" "${dst%/*}" + rmdir "$tmp" + else + echo "$0: $dst is a directory" 1>&2 + exit 1 + fi +fi -- cgit v1.2.3