diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2023-08-05 11:51:25 +0000 |
---|---|---|
committer | Laurent Bercot <ska@appnovation.com> | 2023-08-05 11:51:25 +0000 |
commit | 17c382d1c9d7236c101418060758d2296cc5e17e (patch) | |
tree | fd00e58df0d9d3c70ddd1accfec9e819249c672a | |
download | tipidee-17c382d1c9d7236c101418060758d2296cc5e17e.tar.xz |
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
76 files changed, 5373 insertions, 0 deletions
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 @@ -0,0 +1,2 @@ +Main author: + Laurent Bercot <ska-skaware@skarnet.org> 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/ @@ -0,0 +1,13 @@ +Copyright (c) 2023 Laurent Bercot <ska-skaware@skarnet.org> + +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. @@ -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. @@ -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/$(<F) $@ + +$(DESTDIR)$(sproot)/library.so/lib%.so.$(version_M): $(DESTDIR)$(dynlibdir)/lib%.so.$(version_M) + exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/library.so/$(<F) $@ + +.PHONY: update global-links + +endif + +$(DESTDIR)$(datadir)/%: src/etc/% + exec $(INSTALL) -D -m 644 $< $@ + +$(DESTDIR)$(dynlibdir)/lib%.so $(DESTDIR)$(dynlibdir)/lib%.so.$(version_M): lib%.so.xyzzy + $(INSTALL) -D -m 755 $< $@.$(version) && \ + $(INSTALL) -l $(@F).$(version) $@.$(version_M) && \ + exec $(INSTALL) -l $(@F).$(version_M) $@ + +$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/%: % package/modes + exec $(INSTALL) -D -m 600 $< $@ + grep -- ^$(@F) < package/modes | { read name mode owner && \ + if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \ + chmod $$mode $@ ; } + +$(DESTDIR)$(libdir)/lib%.a: lib%.a.xyzzy + exec $(INSTALL) -D -m 644 $< $@ + +$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h + exec $(INSTALL) -D -m 644 $< $@ + +%.o: %.c + exec $(CC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $< + +%.lo: %.c + exec $(CC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $< + +$(ALL_BINS): + exec $(CC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(EXTRA_LIBS) $(LDLIBS) + +lib%.a.xyzzy: + exec $(AR) rc $@ $^ + exec $(RANLIB) $@ + +lib%.so.xyzzy: + exec $(CC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$(patsubst lib%.so.xyzzy,lib%.so.$(version_M),$@) $^ $(EXTRA_LIBS) $(LDLIBS) + +.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-lib install-include install-data + +.DELETE_ON_ERROR: @@ -0,0 +1,6 @@ +Changelog for tipidee. + +In 0.0.1.0 +---------- + + - Initial release. @@ -0,0 +1,24 @@ +tipidee - a minimalistic web server +----------------------------------- + + tipidee is a minimalistic HTTP/1.1 web server. It is designed to +run under a super-server like inetd or s6-tcpserver. It supports +most of the HTTP protocol. + + See https://skarnet.org/software/tipidee/ for details. + + +* Installation + ------------ + + See the INSTALL file. + + +* Contact information + ------------------- + + Laurent Bercot <ska-skaware at skarnet.org> + + Please use the <skaware at list.skarnet.org> 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 <<EOF +Usage: $0 [OPTION]... [TARGET] + +Defaults for the options are specified in brackets. + +System types: + --target=TARGET configure to run on target TARGET [detected] + --host=TARGET same as --target + +Installation directories: + --prefix=PREFIX main installation prefix [/] + --exec-prefix=EPREFIX installation prefix for executable files [PREFIX] + +Fine tuning of the installation directories: + --dynlibdir=DIR shared library files [PREFIX/lib] + --bindir=BINDIR user executables [EPREFIX/bin] + --libexecdir=DIR package-scoped executables [EPREFIX/libexec] + --libdir=DIR static library files [PREFIX/lib/$package] + --includedir=DIR C header files [PREFIX/include] + + If no --prefix option is given, by default libdir (but not dynlibdir) will be + /usr/lib/$package, and includedir will be /usr/include. + +Dependencies: + --with-sysdeps=DIR use sysdeps in DIR [PREFIX/lib/skalibs/sysdeps] + --with-include=DIR add DIR to the list of searched directories for headers + --with-lib=DIR add DIR to the list of searched directories for static libraries + --with-dynlib=DIR add DIR to the list of searched directories for shared libraries + + If no --prefix option is given, by default sysdeps will be fetched from + /usr/lib/skalibs/sysdeps. + +Optional features: + --enable-shared build shared libraries [disabled] + --disable-static do not build static libraries [enabled] + --disable-allstatic do not prefer linking against static libraries [enabled] + --enable-static-libc make entirely static binaries [disabled] + --disable-all-pic do not build executables or static libs as PIC [enabled] + --enable-slashpackage[=ROOT] assume /package installation at ROOT [disabled] + --enable-absolute-paths do not rely on PATH to access this package's binaries, + hardcode absolute BINDIR/foobar paths instead [disabled] + --enable-nsss use the nsss library for user information [disabled] + +EOF +exit 0 +} + +# Helper functions + +# If your system does not have printf, you can comment this, but it is +# generally not a good idea to use echo. +# See http://etalabs.net/sh_tricks.html +echo () { + IFS=" " + printf %s\\n "$*" +} + +quote () { + tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/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 <<EOF +/* ISC license. */ + +/* Generated by: $cmdline */ + +#ifndef ${package_macro_name}_CONFIG_H +#define ${package_macro_name}_CONFIG_H + +#define ${package_macro_name}_VERSION "$version" +EOF +if $slashpackage ; then + echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\"" + echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix/\"" +elif $abspath ; then + echo "#define ${package_macro_name}_BINPREFIX \"$bindir/\"" + echo "#define ${package_macro_name}_EXTBINPREFIX \"$bindir/\"" +else + echo "#define ${package_macro_name}_BINPREFIX \"\"" + echo "#define ${package_macro_name}_EXTBINPREFIX \"\"" +fi +echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir/\"" +echo +echo "#endif" +exec 1>&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 @@ +<html> + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>tipidee - a small and fast HTTP/1.1 server</title> + <meta name="Description" content="tipidee - a small and fast HTTP/1.1 server" /> + <meta name="Keywords" content="tipidee web server http https laurent bercot ska skarnet.org" /> + <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="//skarnet.org/software/">Software</a><br /> +<a href="//skarnet.org/">skarnet.org</a> +</p> + +<h1> tipidee </h1> + +<h2> What is it ? </h2> + +<p> + tipidee is a web server. It supports HTTP/1.1. It aims to be compliant +with <a href="https://datatracker.ietf.org/doc/html/rfc9112">RFC 9112</a>: +while it only implements a very limited subset of the optional functionality +in HTTP/1.1, it implements all the mandatory parts. +</p> + +<hr /> + +<h2> Why another Web server? </h2> + +<p> + There are two groups of web servers. +</p> + +<p> + The first one is big, powerful servers such as +<a href="https://nginx.org/">nginx</a>, +<a href="https://httpd.apache.org/">Apache httpd</a>, +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. (<a href="https://www.lighttpd.net/">lighttpd</a>, +for instance, was developed as a proof of concept for solving the +<a href="http://www.kegel.com/c10k.html">c10k problem</a>.) +</p> + +<p> + The second one is +</p> + +<h3> And why "tipidee"? </h3> + +<p> + Because <em>h-t-t-p-d</em> is pretty tedious to say out loud, and only +keeping the last syllables makes it easier. +</p> + +<h2> Installation </h2> + +<h3> Requirements </h3> + +<ul> + <li> A POSIX-compliant system with a standard C development environment </li> + <li> GNU make, version 3.81 or later </li> + <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> 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. </li> + <li> Recommended at run-time: <a href="//skarnet.org/software/s6-networking/">s6-networking</a> 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 +<a href="//skarnet.org/software/s6-networking/s6-tcpserver-access.html">s6-tcpserver-access</a> +to provide access control and connection fine-tuning. </li> +</ul> + +<h3> Licensing </h3> + +<p> + tipidee is free software. It is available under the +<a href="https://opensource.org/licenses/ISC">ISC license</a>. +</p> + +<h3> Download </h3> + +<ul> + <li> The current released version of tipidee is +<a href="tipidee-0.0.1.0.tar.gz">0.0.1.0</a>. </li> + <li> You can checkout a copy of the +<a href="//git.skarnet.org/cgi-bin/cgit.cgi/tipidee/">tipidee +git repository</a>: +<pre> git clone git://git.skarnet.org/tipidee </pre> </li> + <li> There's also a +<a href="https://github.com/skarnet/tipidee">GitHub mirror</a> +of the tipidee git repository. </li> +</ul> + +<h3> Compilation </h3> + +<ul> + <li> See the enclosed INSTALL file for installation details. </li> +</ul> + +<h3> Upgrade notes </h3> + +<ul> + <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between +the previous versions of tipidee and the current one. </li> +</ul> + +<hr /> + +<h2> Reference </h2> + +<h3> Commands </h3> + +<ul> +<li><a href="tipideed.html">The <tt>tipideed</tt> program</a></li> +<li><a href="tipidee-config.html">The <tt>tipidee-config</tt> program</a></li> +</ul> + +<h3> Internal commands </h3> + +<ul> +<li><a href="tipidee-config-preprocess.html">The <tt>tipidee-config-preprocess</tt> internal program</a></li> +</ul> + +<h2> Related resources </h2> + +<ul> + <li> <tt>tipidee</tt> is discussed on the +<a href="//skarnet.org/lists/#skaware">skaware</a> mailing-list. </li> +</ul> + +</body> +</html> 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 @@ +<html> + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>tipidee: how to upgrade</title> + <meta name="Description" content="tipidee: how to upgrade" /> + <meta name="Keywords" content="tipidee installation upgrade" /> + <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">tipidee</a><br /> +<a href="//skarnet.org/software/">Software</a><br /> +<a href="//skarnet.org/">skarnet.org</a> +</p> + +<h1> What has changed in tipidee </h1> + +<h2> in 0.0.1.0 </h2> + +<ul> + <li> Initial release. </li> +</ul> + +</body> +</html> 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 <stdint.h> +#include <string.h> + +#include <skalibs/stralloc.h> +#include <skalibs/strerr.h> + +#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 <stdint.h> +#include <string.h> +#include <errno.h> + +#include <skalibs/gensetdyn.h> +#include <skalibs/avltree.h> +#include <skalibs/cdbmake.h> +#include <skalibs/strerr.h> + +#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 <stddef.h> + +#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 <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <skalibs/uint32.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/skamisc.h> + +#include <tipidee/config.h> +#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 <stdint.h> +#include <string.h> + +#include <skalibs/buffer.h> +#include <skalibs/stralloc.h> +#include <skalibs/cdbmake.h> + +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 <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#include <skalibs/uint32.h> +#include <skalibs/sgetopt.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/direntry.h> +#include <skalibs/djbunix.h> +#include <skalibs/skamisc.h> +#include <skalibs/avltree.h> + +#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 <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> /* rename() */ +#include <errno.h> +#include <signal.h> + +#include <skalibs/posixplz.h> +#include <skalibs/types.h> +#include <skalibs/sgetopt.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/sig.h> +#include <skalibs/djbunix.h> + +#include <tipidee/config.h> +#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 <stddef.h> +#include <stdint.h> + +#include <skalibs/buffer.h> +#include <skalibs/tai.h> +#include <skalibs/stralloc.h> + +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 <stddef.h> +#include <stdint.h> + +#include <skalibs/uint16.h> +#include <skalibs/cdb.h> + +#include <tipidee/uri.h> + +#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 <stddef.h> +#include <stdint.h> + +#include <skalibs/disize.h> +#include <skalibs/buffer.h> +#include <skalibs/tai.h> +#include <skalibs/avltreen.h> + +#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 <stddef.h> +#include <stdint.h> + +#include <skalibs/gccattributes.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> + +#include <tipidee/rql.h> + +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 <skalibs/buffer.h> +#include <skalibs/tai.h> + +#include <tipidee/method.h> +#include <tipidee/uri.h> + +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 <tipidee/body.h> +#include <tipidee/config.h> +#include <tipidee/conf.h> +#include <tipidee/headers.h> +#include <tipidee/method.h> +#include <tipidee/response.h> +#include <tipidee/rql.h> +#include <tipidee/uri.h> + +#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 <stddef.h> +#include <stdint.h> + +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 <string.h> +#include <errno.h> + +#include <skalibs/types.h> +#include <skalibs/stralloc.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/body.h> + +#include <skalibs/posixishard.h> + +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 <skalibs/cdb.h> + +#include <tipidee/conf.h> + +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 <errno.h> +#include <string.h> + +#include <skalibs/cdb.h> +#include <skalibs/lolstdio.h> + +#include <tipidee/conf.h> + +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 <errno.h> +#include <string.h> + +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +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 <errno.h> +#include <string.h> + +#include <tipidee/conf.h> + +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 <errno.h> +#include <string.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +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 <errno.h> + +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +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 <errno.h> + +#include <skalibs/uint32.h> +#include <skalibs/cdb.h> + +#include <tipidee/conf.h> + +#include <skalibs/posixishard.h> + +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 <skalibs/cdb.h> + +#include <tipidee/conf.h> + +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 <stddef.h> +#include <limits.h> + +#include <skalibs/types.h> + +#include <tipidee/headers.h> + +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 <stdint.h> +#include <strings.h> + +#include <skalibs/avltreen.h> + +#include <tipidee/headers.h> + +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 <stdint.h> +#include <string.h> +#include <strings.h> +#include <errno.h> + +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/error.h> +#include <skalibs/avltreen.h> +#include <skalibs/unix-timed.h> +// #include <skalibs/lolstdio.h> + +#include <tipidee/headers.h> + +/* + +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 <stdint.h> + +#include <skalibs/avltreen.h> + +#include <tipidee/headers.h> + +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 <tipidee/method.h> + +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 <string.h> + +#include <tipidee/method.h> + +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 <tipidee/method.h> + +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 <stddef.h> + +#include <skalibs/types.h> +#include <skalibs/buffer.h> + +#include <tipidee/method.h> +#include <tipidee/rql.h> +#include <tipidee/response.h> + +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[] = "<html>\n<head><title>" ; + static char const txt2[] = "</title></head>\n<body>\n<h1> " ; + static char const txt3[] = " </h1>\n<p>\n" ; + static char const txt4[] = "\n</p>\n</body>\n</html>\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 <string.h> +#include <stdlib.h> + +#include <tipidee/config.h> +#include <tipidee/response.h> + +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 <stdint.h> + +#include <skalibs/buffer.h> + +#include <tipidee/config.h> +#include <tipidee/response.h> + +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 <string.h> +#include <time.h> + +#include <skalibs/tai.h> +#include <skalibs/djbtime.h> + +#include <tipidee/response.h> + +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 <skalibs/types.h> +#include <skalibs/buffer.h> + +#include <tipidee/response.h> + +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 <stdint.h> +#include <string.h> +#include <strings.h> + +#include <skalibs/types.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/unix-timed.h> +// #include <skalibs/lolstdio.h> + +#include <tipidee/method.h> +#include <tipidee/uri.h> +#include <tipidee/rql.h> + +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 <stdint.h> +#include <string.h> + +#include <skalibs/uint16.h> +#include <skalibs/bytestr.h> +#include <skalibs/fmtscan.h> +#include <skalibs/lolstdio.h> + +#include <tipidee/uri.h> + + +/* + + 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 <unistd.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <signal.h> + +#include <skalibs/gccattributes.h> +#include <skalibs/posixplz.h> +#include <skalibs/types.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/error.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/strerr.h> +#include <skalibs/stralloc.h> +#include <skalibs/djbunix.h> +#include <skalibs/iopause.h> +#include <skalibs/env.h> +#include <skalibs/exec.h> +#include <skalibs/unix-timed.h> +#include <skalibs/lolstdio.h> + +#include <tipidee/method.h> +#include <tipidee/headers.h> +#include <tipidee/response.h> +#include <tipidee/uri.h> +#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 <skalibs/sysdeps.h> +#include <skalibs/nonposix.h> + +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> + +#include <skalibs/types.h> +#include <skalibs/strerr.h> + +#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 <unistd.h> + +#include <skalibs/uint16.h> +#include <skalibs/types.h> +#include <skalibs/strerr.h> + +#include <tipidee/method.h> +#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 <string.h> + +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/response.h> +#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 <skalibs/uint64.h> +#include <skalibs/types.h> +#include <skalibs/buffer.h> +#include <skalibs/djbunix.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/method.h> +#include <tipidee/response.h> +#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 <unistd.h> + +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/rql.h> +#include <tipidee/response.h> + +#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 <skalibs/sysdeps.h> + +#ifdef SKALIBS_HASSPLICE + +#include <skalibs/nonposix.h> + +#include <fcntl.h> +#include <stdint.h> +#include <unistd.h> + +#include <skalibs/strerr.h> +#include <skalibs/djbunix.h> +#include <skalibs/unix-timed.h> + +#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 <sys/uio.h> + +#include <skalibs/allreadwrite.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> + +#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 <sys/types.h> +#include <stdint.h> + +#include <skalibs/gccattributes.h> +#include <skalibs/uint64.h> +#include <skalibs/stralloc.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> + +#include <tipidee/tipidee.h> + +#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 <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/stat.h> + +#include <skalibs/env.h> +#include <skalibs/uint16.h> +#include <skalibs/types.h> +#include <skalibs/bytestr.h> +#include <skalibs/sgetopt.h> +#include <skalibs/buffer.h> +#include <skalibs/error.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> +#include <skalibs/ip46.h> +#include <skalibs/sig.h> +#include <skalibs/stralloc.h> +#include <skalibs/djbunix.h> +#include <skalibs/avltreen.h> +#include <skalibs/unix-timed.h> +#include <skalibs/lolstdio.h> + +#include <tipidee/tipidee.h> +#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 <string.h> + +#include <skalibs/types.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/tai.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/method.h> +#include <tipidee/response.h> +#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 |