pax_global_header00006660000000000000000000000064142406654730014525gustar00rootroot0000000000000052 comment=78b15fab4e25ef5a6829261c3eb73ce992ba9fc5 merecat-2.31+git20220513+ds/000077500000000000000000000000001424066547300150725ustar00rootroot00000000000000merecat-2.31+git20220513+ds/.github/000077500000000000000000000000001424066547300164325ustar00rootroot00000000000000merecat-2.31+git20220513+ds/.github/SECURITY.md000066400000000000000000000010751424066547300202260ustar00rootroot00000000000000Security Policy =============== Supported Versions ------------------ Merecat httpd is a small project, as such we have no possibility to support older versions. The only supported version is the latest released on GitHub: Reporting a Vulnerability ------------------------- Contact the project's main author and owner to report and discuss vulnerabilities. See the [README][] in the projects top directory, also part of the distribution archive. [README]: https://github.com/troglobit/merecat/blob/master/README.md merecat-2.31+git20220513+ds/.github/workflows/000077500000000000000000000000001424066547300204675ustar00rootroot00000000000000merecat-2.31+git20220513+ds/.github/workflows/build.yml000066400000000000000000000027461424066547300223220ustar00rootroot00000000000000name: Bob the Builder # Run on all branches, including all pull requests, except the 'dev' # branch since that's where we run Coverity Scan (limited tokens/day) on: push: branches: - '**' - '!dev' pull_request: branches: - '**' jobs: build: # Verify we can build on latest Ubuntu with both gcc and clang name: ${{ matrix.compiler }} runs-on: ubuntu-latest strategy: matrix: compiler: [gcc, clang] fail-fast: false env: MAKEFLAGS: -j3 CC: ${{ matrix.compiler }} steps: - name: Install dependencies run: | sudo apt-get -y update sudo apt-get -y install tree libconfuse-dev libssl-dev zlib1g-dev php-cgi - uses: actions/checkout@v2 - name: Create configure script run: | ./autogen.sh - name: Build w/o HTTPS run: | ./configure --prefix=/ --without-ssl --enable-htaccess --enable-htpasswd make V=1 - name: Build w/ HTTPS run: | make clean ./configure --prefix=/ --enable-htaccess --enable-htpasswd make V=1 - name: Install to ~/tmp and Inspect run: | DESTDIR=~/tmp make install-strip tree ~/tmp ldd ~/tmp/sbin/merecat size ~/tmp/sbin/merecat ~/tmp/sbin/merecat -h - name: Run tests run: | # Tests must currently not run in parallel make -j1 check || (cat tests/test-suite.log; false) merecat-2.31+git20220513+ds/.github/workflows/codeql-analysis.yml000066400000000000000000000047311424066547300243070ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '32 0 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'cpp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v2 - name: Install dependencies run: | sudo apt-get -y update sudo apt-get -y install tree libconfuse-dev libssl-dev zlib1g-dev php-cgi - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 merecat-2.31+git20220513+ds/.github/workflows/container.yml000066400000000000000000000022241424066547300231740ustar00rootroot00000000000000name: Container Claus on: push: branches: - 'master' tags: - 'v[0-9]+.[0-9]+*' jobs: docker: runs-on: ubuntu-latest permissions: packages: write contents: read env: MAKEFLAGS: -j3 IMAGE_NAME: merecat steps: - uses: actions/checkout@v2 - name: Build image run: docker build . --file Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" - name: Log in to registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - name: Push image run: | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME # Change all uppercase to lowercase IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') # Strip git ref prefix from version VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') # Use Docker `latest` tag convention [ "$VERSION" == "master" ] && VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION merecat-2.31+git20220513+ds/.github/workflows/coverity.yml000066400000000000000000000052711424066547300230630ustar00rootroot00000000000000name: Coverity Scan on: push: branches: - 'dev' env: PROJECT_NAME: merecat CONTACT_EMAIL: troglobit@gmail.com COVERITY_NAME: troglobit-merecat COVERITY_PROJ: troglobit%2Fmerecat jobs: coverity: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Fetch latest Coverity Scan MD5 id: var env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | wget -q https://scan.coverity.com/download/cxx/linux64 \ --post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \ -O coverity-latest.tar.gz.md5 export MD5=$(cat coverity-latest.tar.gz.md5) echo "Got MD5 $MD5" echo ::set-output name=md5::${MD5} - uses: actions/cache@v2 id: cache with: path: coverity-latest.tar.gz key: ${{ runner.os }}-coverity-${{ steps.var.outputs.md5 }} restore-keys: | ${{ runner.os }}-coverity-${{ steps.var.outputs.md5 }} ${{ runner.os }}-coverity- ${{ runner.os }}-coverity - name: Download Coverity Scan env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | if [ ! -f coverity-latest.tar.gz ]; then wget -q https://scan.coverity.com/download/cxx/linux64 \ --post-data "token=$TOKEN&project=${COVERITY_PROJ}" \ -O coverity-latest.tar.gz else echo "Latest Coverity Scan available from cache :-)" md5sum coverity-latest.tar.gz fi mkdir coverity tar xzf coverity-latest.tar.gz --strip 1 -C coverity - name: Install dependencies run: | sudo apt-get -y update sudo apt-get -y install tree libconfuse-dev libssl-dev zlib1g-dev php-cgi - name: Configure run: | ./autogen.sh ./configure - name: Build run: | export PATH=`pwd`/coverity/bin:$PATH cov-build --dir cov-int make - name: Submit results to Coverity Scan env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | tar czvf ${PROJECT_NAME}.tgz cov-int curl \ --form project=${COVERITY_NAME} \ --form token=$TOKEN \ --form email=${CONTACT_EMAIL} \ --form file=@${PROJECT_NAME}.tgz \ --form version=trunk \ --form description="${PROJECT_NAME} $(git rev-parse HEAD)" \ https://scan.coverity.com/builds?project=${COVERITY_PROJ} - name: Upload build.log uses: actions/upload-artifact@v2 with: name: coverity-build.log path: cov-int/build-log.txt merecat-2.31+git20220513+ds/.github/workflows/release.yml000066400000000000000000000034641424066547300226410ustar00rootroot00000000000000name: Release General on: push: tags: - 'v[0-9]+.[0-9]+*' jobs: release: name: Create GitHub release runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} release_id: ${{ steps.create_release.outputs.id }} steps: - uses: actions/checkout@v2 - name: Extract ChangeLog entry ... # Hack to extract latest entry for body_path below run: | awk '/-----*/{if (x == 1) exit; x=1;next}x' ChangeLog.md \ |head -n -1 > release.md cat release.md - name: Create release ... id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Merecat httpd ${{ github.ref }} body_path: release.md draft: false prerelease: false tarball: name: Build and upload release tarball needs: release if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install dependencies ... run: | sudo apt-get -y update sudo apt-get -y install tree libconfuse-dev libssl-dev zlib1g-dev php-cgi - name: Creating Makefiles ... run: | ./autogen.sh ./configure - name: Build release ... run: | make release mkdir -p artifacts/ mv ../*.tar.* artifacts/ - name: Upload release artifacts ... uses: skx/github-action-publish-binaries@release-0.15 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: releaseId: ${{ needs.release.outputs.release_id }} args: artifacts/* merecat-2.31+git20220513+ds/.gitignore000066400000000000000000000003561424066547300170660ustar00rootroot00000000000000*~ *.o .config .unpacked .gdb_history GPATH GRTAGS GSYMS GTAGS aclocal.m4 autom4te.cache compile config.h config.h.in config.log config.status configure depcomp install-sh merecat.service missing stamp-h1 test-driver Makefile Makefile.in merecat-2.31+git20220513+ds/ChangeLog.md000066400000000000000000000110441424066547300172430ustar00rootroot00000000000000Change Log ========== All relevant changes are documented in this file. [v2.32][UNRELEASED] ------------------- Notable new features: multiple server support from one process, HTTPS, HTTP/1.1 keep-alive, and built-in gzip deflate compression using zlib. ### Changes - Add support for HTTPS, works with certificates from Let's Encrypt - Add support for multiple servers, listen to different ports - Add support for built-in HTTP redirect, e.g. from HTTP to HTTPS - Add support for server location directive, similar to nginx but with security limitations and native vhost support native to thttpd - Add gzip deflate compression when built with zlib, also compress HEAD as well as GET requests - Add true `Connection: keep-alive` support - Add missing `Vary: Accept-Encoding` header - CGI: Allow handling other HTTP methods besides GET/HEAD/POST, from thttpd v2.29, change by Jef Poskanzer - CGI: Allow `:PORT` in `HTTP_POST`, like Apache - CGI: Allow trailing slash in `PATH_INFO`, like Apache - CGI: Change default `CGI_PATTERN` from disabled to `**.cgi|/cgi-bin/*` - CGI: Add support for looking for an `index.cgi` index file - CGI: Add several missing standard CGI/1.1 environment variables, see the file doc/cgi.txt for details - PHP: - Add support for `php-cgi` and `index.php` index file - Add support for PHP pattern matching, run php-cgi if `**.php` - Server-Side Includes (SSI): - Add support for SSI pattern matching, run cgi-bin/ssi if `**.shtml` - Add support for silencing default SSI `errmsg` - Add support for looking for `index.shtml` index file - Dot files are no longer shown in dir listings, use the `merecat.conf` setting `list-dotfiles = true` to enable - Server stats are no longer periodically sent to syslog, re-enable in `merecat.conf` if you need the `STATS_TIME` feature - Apply Debian thttpd `SIGBUS` patch for reading from NFS - Add `-I IDENT` command line option to override program identity. This change makes it possible to change syslog, PID file name, *and* `.conf` file name. Useful when running multiple instances of Merecat - Add `--enable-msie-padding` to `configure` script - Add `.htaccess` support, limited to IPv4. Feature by Felix J. Ogris - Allow `.htpasswd` file to be symlinked - DOC: How to use `.htpasswd` and virtual hosts - DOC: Added section on how to optimize performance - Update MIME types, e.g. Ogg video, 7zip, svg - Add Dockerfile for ease of deployment in limited setups - Add cute cat default favicon - Built-in icons for FTP dir listings; folder, file, etc. - Refactor, deprecated POSIX API's, e.g. `bzero() --> memset()` - Enable `SO_REUSEPORT` if available, useful for load balancing ### Fixes - Fix CVE-2017-17663, buffer overrun in htpasswd tool, from thttpd v2.28 - Fixes for non GNU C libraries like musl: `__progname`, `%m`, etc. - Fix `X-Forwarded-For` when using IPv6, thanks to Steve Kemp! - Debian packaging fixes - Make sure both `.htpasswd` *and* `.htaccess` are declared forbidden files and not allowed to be downloaded or shown in directory listings - Use `memmove()` instead of `strcpy()` for possibly overlapping regions - Cleanup of default `merecat.conf`, default disabled options to their built-in default values - Spelling fixes and major documentation cleanup [v2.31][] - 2016-11-06 ---------------------- The "it works now" release. ### Changes - Sort directories first in dir listings - Include systemd unit file - Add `debian/` packaging, easy to rebuild and replace for others - Add `--enable-public-html` to enable `~user/public_html` dirs - Support for shared `WEBROOT/cgi-bin` as fallback for vhosts - Update default landing page ### Fixes - Add missing CSS and jpeg files to install - Fix dependency tracking when reconfiguring - Fix `.conf` file parser bugs reported by Gaetan Bisson - Fix missing `HAVE_LIBCONFUSE` #define causing `.conf` file support to not be built, reported by Gaetan Bisson - Fix malplaced call to `cfg_free()` in .conf file parser, reported by Gaetan Bisson - Update man page and other documentation with missing quotes around CGI pattern, issue reported by Gaetan Bisson - Fix syslog warning: bind 0.0.0.0: Address already in use [v2.30][] - 2016-10-09 ---------------------- Initial release. Based on [sthttpd][] master, 2015-07-22. [UNRELEASED]: https://github.com/troglobit/merecat/compare/v2.31...HEAD [v2.32]: https://github.com/troglobit/merecat/compare/v2.31...v2.32 [v2.31]: https://github.com/troglobit/merecat/compare/v2.30...v2.31 [v2.30]: https://github.com/troglobit/merecat/compare/v2.29...v2.30 [sthttpd]: https://github.com/blueness/sthttpd/ merecat-2.31+git20220513+ds/Dockerfile000066400000000000000000000012371424066547300170670ustar00rootroot00000000000000FROM alpine:3.6 # Build depends RUN apk add --no-cache gcc musl-dev make automake autoconf zlib-dev # Install from GIT WORKDIR . ADD . /merecat RUN cd merecat/; ./build.sh; make install-strip; cd ..; rm -rf merecat # Alternatively, install from released tarball #RUN wget https://ftp.troglobit.com/merecat/merecat-2.32.tar.xz; \ # tar xf merecat-2.32.tar.bz2; \ # cd merecat-2.32/; \ # ./build.sh; \ # make install-strip # Clean up container # m4 perl binutils binutils-libs bmp isl libgomp libatomic pkgconf RUN apk del --purge gcc musl-dev make automake autoconf zlib-dev EXPOSE 80 VOLUME /var/www ENTRYPOINT merecat -p 80 -n /var/www merecat-2.31+git20220513+ds/LICENSE000066400000000000000000000025531424066547300161040ustar00rootroot00000000000000Copyright (C) 1995-2015 Jef Poskanzer Copyright (C) 2016-2021 Joachim Wiberg All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. merecat-2.31+git20220513+ds/Makefile.am000066400000000000000000000034221424066547300171270ustar00rootroot00000000000000SUBDIRS = src www man tests doc_DATA = README.md LICENSE ChangeLog.md EXTRA_DIST = README.md LICENSE ChangeLog.md throttle.conf merecat.conf DISTCHECK_CONFIGURE_FLAGS = --with-systemd=$$dc_install_base/$(systemd) if HAVE_CONFUSE dist_sysconf_DATA = merecat.conf endif if HAVE_SYSTEMD systemd_DATA = merecat.service endif ## Update html versions of man pages doc: @for file in htpasswd.1 merecat.8 merecat.conf.5 ssi.8; do \ out=www/$$file.html; \ sed "s/%TITLE%/$$file/" < www/header.html > $$out; \ mandoc -T html -O fragment -O man=%N.%S.html \ man/$$file >> $$out; \ cat www/footer.html >> $$out; \ done ## Generate MD5 checksum file MD5 = md5sum md5-dist: @for file in $(DIST_ARCHIVES); do \ $(MD5) $$file > ../$$file.md5; \ mv $$file ../; \ done ## Check if tagged in git release-hook: if [ ! `git tag | grep $(PACKAGE_VERSION)` ]; then \ echo; \ printf "\e[1m\e[41mCannot find release tag $(PACKAGE_VERSION)\e[0m\n"; \ printf "\e[1m\e[5mDo release anyway?\e[0m "; read yorn; \ if [ "$$yorn" != "y" -a "$$yorn" != "Y" ]; then \ printf "OK, aborting release.\n"; \ exit 1; \ fi; \ echo; \ else \ echo; \ printf "\e[1m\e[42mFound GIT release tag $(PACKAGE_VERSION)\e[0m\n"; \ printf "\e[1m\e[44m>>Remember to push tags!\e[0m\n"; \ echo; \ fi # Target to run when building a release release: distcheck release-hook md5-dist @echo @echo "Resulting release files:" @echo "=================================================================" @for file in $(DIST_ARCHIVES); do \ printf "$$file \tDistribution tarball\n"; \ printf "$$file.md5\t"; cat ../$$file.md5 | cut -f1 -d' '; \ done merecat-2.31+git20220513+ds/README.md000066400000000000000000000273161424066547300163620ustar00rootroot00000000000000Merecat httpd ∴ Embedded Web Server =================================== [![License Badge][]][License] [![GitHub Status][]][GitHub] [![Coverity Status]][Coverity Scan] http://imgur.com/user/SunShot [Merecat][] started out as a pun at [Mongoose][], but is now useful for actual web serving purposes. It is however not a real [Meerkat][], merely yet another copycat, forked from the great [thttpd][] created by Jef Poskanzer. Merecat httpd expands on the features originally offered by thttpd, but still has a limited feature set: - Virtual hosts - Basic `.htpassd` and `.htaccess` support - URL-traffic-based throttling - CGI/1.1 - HTTP/1.1 Keep-alive - Built-in gzip deflate using zlib - HTTPS support using OpenSSL/LibreSSL, works with [Let's Encrypt][]! - Dual server support, both HTTP/HTTPS from one process - HTTP redirect, to gently redirect from HTTP server to HTTPS - Native PHP support, using `php-cgi` if enabled in `merecat.conf` The resulting footprint (~140 kiB) makes it quick and suitable for small and embedded systems! Merecat is available as free/open source software under the simplified 2-clause [BSD license][license]. For more information, see the manual page `merecat(8)`, or the [FAQ][]. The rest of this README covers some basic functions and recommendations. For more in-depth use-case examples, see the following HowTos: - https://troglobit.com/howtos/merecat-basic-cig-in-c/ - https://troglobit.com/howtos/merecat-and-lets-encrypt/ - https://troglobit.com/howtos/merecat-and-ikiwiki/ - https://troglobit.com/howtos/merecat-and-cgit/ Docker ------ Try out [Docker Merecat](https://hub.docker.com/r/troglobit/merecat/) safely isolated from the rest of the system, with easy deployment. Authentication -------------- To protect a directory in your `~USERNAME/public_html/`, create the file `.htpasswd` using the included `htpasswd` tool: ```shell user@example:~/> cd public_html/Downloads user@example:~/public_html/Downloads/> htpasswd -c .htpasswd friend Changing password for user friend New password: ***** Re-type new password: ***** ``` Enable this feature, and user home directories, with the `configure` script. See more on this in the [Features](#features) section below. Virtual Hosts ------------- Setting up virtual hosts on a server can be a bit of a hassle with other web servers. With Merecat you simply create directories for each host in the web server root: ``` /var/www/ |-- icons/ |-- cgi-bin/ |-- errors/ | `-- err404.html |-- ftp.example.com/ `- www.example.com/ ``` Edit `/etc/merecat.conf`: ```conf virtual-host = true cgi "/cgi-bin/*|**.cgi" { enabled = true } ``` Now the web server root, `/var/www/`, no longer serves files, only virtual host directories do, execpt for the shared files in `icons/`, `cgi-bin/`, and `errors/`. On Linux bind mounts can be used to set up FTP and web access to the same files. Example `/etc/fstab`: ``` /srv/ftp /var/www/ftp.example.com none defaults,bind 0 0 ``` Optimizing Performance ---------------------- There are many tricks to optimizing the performance of your web server. One of the most important ones is browser caching. Merecat supports both `ETag:` and `Cache-Control:`, however to enable the latter you need to define the `max-age` setting in `/etc/merecat.conf`: ```conf max-age = 3600 # One hour ``` The value is completely site dependent. For an embedded system you might want to set it to the maximum value, whereas for other scenarios you will likely want something else. By default this is disabled (0). Another trick is to employ `gzip` compression. Merecat has built-in support for serving HTML, CSS, and other `text/*` files if there is a `.gz` version of the same file. Here is an example of how to compress relevant files: ```shell root@example:~/> cd /var/www/ root@example:/var/www/> for file in `find . -name '*.html' -o -name '*.css'`; do \ gzip -c $file > $file.gz; done ``` This approach is more CPU friendly than letting Merecat "deflate" files on the fly, which it otherwise does. HTTPS Support ------------- If `configure` finds OpenSSL installed, HTTPS support is enabled, this can be disabled using `--without-ssl`. However, to gain access to the SSL/TLS settings you also need support for `merecat.conf`, so you must install [libConfuse][]. See below for all Build Requirements. The HTTPS support has SSLv2, SSLv3, and TLSv1 disabled (hard coded) by default. Only TLSv2 and later will be enabled and negotiated on a per client basis. To set up Merecat for HTTPS the following `/etc/merecat.conf` settings must be enabled: ```conf server secure { port = 443 ssl { certfile = /etc/letsencrypt/live/example.com/fullchain.pem keyfile = /etc/letsencrypt/live/example.com/privkey.pem dhfile = /etc/letsencrypt/live/example.com/dhparam.pem } } ``` ### Let's Encrypt Merecat fully supports [Let's Encrypt][] certificates, including HTTP-01 renewals. Use the server location directive: ```conf server default { port = 80 location "/.well-known/acme-challenge/**" { path = "letsencrypt/.well-known/acme-challenge/" } redirect "/**" { code = 301 location = "https://$host$request_uri$args" } } ``` The `path` must be relative to the server root directory. Use bind mounts to get `/var/lib/letsencrypt` into your server root. This way we can ensure `certbot` only writes to its own directory and cannot write to any file in the server root. Then run `certbot` with the following arguments and then add all virtual hosts you want to support from Merecat: ```shell root@example:/var/www/> certbot certonly --webroot --webroot-path /var/lib/letsencrypt ``` For a HowTo see: - https://troglobit.com/howtos/merecat-and-lets-encrypt/ ### Self-signed Certificate To create a self signed certificate and enable perfect forward secrecy, PFS, i.e. Diffie-Helman paramters (optional), use the `openssl` tool as shown below. Notice the use of a sub-shell with `openssl.cnf` where most of the certificate settings are, and more importantly notice the use of `subjectAltName`, or SAN. The latter is required by most browsers today. ```shell root@example:/var/www/> mkdir private certs root@example:/var/www/> openssl req -x509 -newkey rsa:4096 -nodes \ -keyout private/server.key -new -out certs/server.pem \ -subj /CN=www.acme.com -reqexts SAN -extensions SAN \ -sha256 -days 3650 -config <(cat /etc/ssl/openssl.cnf \ <(printf '[SAN]\nsubjectAltName=DNS:www.acme.com')) root@example:/var/www/> openssl dhparam -out certs/dhparm.pem 4096 ``` HTTP Redirect ------------- For a setup with two servers, the following example can be used to run HTTPS on port 4443, HTTP on port 8080 and redirect to the HTTPS server on any access: ```conf server secure { port = 4443 ssl { certfile = certs/server.pem keyfile = private/server.key dhfile = certs/dhparm.pem } } server default { port = 8080 redirect "/**" { code = 303 location = "https://$host:4443$request_uri$args" } } ``` Supported HTTP redirect codes are: 301, 302, 303, and 307. The location setting supports three nginx style variables as shown in the example. Please note the quotes around the pattern, or the .conf parser will think the pattern is a C-style comment. Build Requirements ------------------ Merecat depends on a few external libraries, if enabled, e.g. OpenSSL, zlib, and [libConfuse][]. On Debian/Ubuntu systems you can install the dependencies with: ```shell user@example:~/> sudo apt install pkg-config libconfuse-dev libssl-dev zlib1g-dev ``` If you build the deps. from source, they may default to use an install prefix of `/usr/local`. Non Debian/Ubuntu systems rarely support this GNU standard, so here is how you reference it for the Merecat `configure` script: ```shell user@example:~/merecat/> PKG_CONFIG_LIBDIR=/usr/local/lib/pkgconfig ./configure ``` To build Merecat without support for `/etc/merecat.conf`: ```shell user@example:~/merecat/> ./configure --without-config ``` If you build from GIT sources and not a released tarball, then remember: ```shell user@example:~/merecat/> ./autogen.sh ``` To install `httpd` into `/usr/sbin/`, default index and icons into `/var/www`, and config file to `/etc/merecat.conf`: ```shell user@example:~/merecat/> ./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc user@example:~/merecat/> make user@example:~/merecat/> sudo make install ``` Cross compiling Merecat for an another target is possible by setting the `--host` flag to the configure script. This is well documented in the [GNU Documentation][configure]. Note: ususally the `--build` system is automatically detected. > Merecat builds are silent by default. For detailed compiler output, > disable silent mode with `configure --disable-silent-rules`, or build > with `make V=1`. Features -------- Merecat consists of a front-end, `merecat.c`, and a standalone HTTP library, `libhttpd.c`, which can be tweaked in various ways and used for embedding a web server in another application if needed. The most common options are available from the `merecat` command line and the `merecat.conf` configuration file. Other, less common options, can be enabled using the `configure` script: ``` --enable-builtin-icons Enable built-in icons for dir listings --enable-htaccess Enable .htaccess files for access control --enable-htpasswd Enable .htpasswd files for authentication --enable-public-html Enable $HOME/public_html as ~USERNAME/ --enable-msie-padding Add padding to error messages for Internet Explorer --disable-dirlisting Disable directory listings when no index file is found --without-config Disable /etc/merecat.conf support using libConfuse --without-ssl Disable HTTPS support, default: enabled --without-symlinks Disable httpd and in.httpd symlinks to merecat --without-zlib Disable mod_deflate (gzip) using zlib ``` The source file `merecat.h` has even more features that can be tweaked, some of those are mentioned in the man page, but the header file has very useful comments as well. Origin & References ------------------- Merecat is a stiched up fork of [sthttpd][] with lots of lost patches found lying around the web. The sthttpd project in turn is a fork from the original [thttpd][] -- the tiny/turbo/throttling HTTP server. * [thttpd][] was created by Jef Poskanzer * [sthttpd][] was spawned by Anthony G. Basile * [Merecat][] is maintained by Joachim Wiberg [Merecat]: https://merecat.troglobit.com [Meerkat]: https://en.wikipedia.org/wiki/Meerkat [license]: https://github.com/troglobit/merecat/blob/master/LICENSE [Mongoose]: https://github.com/cesanta/mongoose [Let's Encrypt]: https://letsencrypt.org/ [libConfuse]: https://github.com/martinh/libconfuse/ [configure]: https://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html [FAQ]: http://halplant.com:2001/server/thttpd_FAQ.html [thttpd]: http://www.acme.com/software/thttpd/ [sthttpd]: https://github.com/blueness/sthttpd/ [License]: https://en.wikipedia.org/wiki/BSD_licenses [License Badge]: https://img.shields.io/badge/License-BSD%202--Clause-orange.svg [GitHub]: https://github.com/troglobit/merecat/actions/workflows/build.yml/ [GitHub Status]: https://github.com/troglobit/merecat/actions/workflows/build.yml/badge.svg [Coverity Scan]: https://scan.coverity.com/projects/18686 [Coverity Status]: https://scan.coverity.com/projects/18686/badge.svg merecat-2.31+git20220513+ds/autogen.sh000077500000000000000000000000541424066547300170720ustar00rootroot00000000000000#!/bin/sh autoreconf -W portability -visfm merecat-2.31+git20220513+ds/build.sh000077500000000000000000000003771424066547300165370ustar00rootroot00000000000000if [ ! -x configure ]; then ./autogen.sh fi ./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc --enable-builtin-icons \ --without-config --without-ssl --without-symlinks --enable-htaccess --enable-htpasswd make -j5 clean make -j5 merecat-2.31+git20220513+ds/configure.ac000066400000000000000000000164411424066547300173660ustar00rootroot00000000000000AC_PREREQ([2.68]) AC_INIT([Merecat httpd], [2.32-rc4], [https://github.com/troglobit/merecat/issues], [merecat]) AC_CONFIG_AUX_DIR(aux) AM_INIT_AUTOMAKE([1.11 foreign dist-xz]) AM_SILENT_RULES([yes]) AC_CONFIG_SRCDIR([src/libhttpd.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile merecat.service src/Makefile man/Makefile tests/Makefile www/Makefile www/cgi-bin/Makefile www/icons/Makefile] www/img/Makefile) # Use pkg-config to check for some deps; libConfuse, libssl PKG_PROG_PKG_CONFIG # Checks for programs. AC_PROG_CC AC_PROG_LN_S AC_PROG_INSTALL AC_PROG_MAKE_SET AC_PROG_RANLIB m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) # Checks for libraries. AC_CHECK_LIB(crypt, crypt) AC_CHECK_LIB(rt, clock_gettime) AC_CHECK_LIB(resolv, hstrerror) # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h grp.h memory.h netdb.h netinet/in.h osreldate.h paths.h poll.h stddef.h stdlib.h string.h termios.h sys/devpoll.h sys/event.h sys/param.h sys/poll.h sys/socket.h sys/time.h syslog.h unistd.h]) AC_CHECK_HEADER_STDBOOL AC_HEADER_TIME AC_HEADER_DIRENT AC_C_INLINE # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_INT64_T AC_TYPE_MODE_T AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T # Check for missing/broken API's, which we can replace, e.g. lib/lstat.c AC_REPLACE_FUNCS([strlcpy strlcat tempfile]) AC_CONFIG_LIBOBJ_DIR([lib]) # Checks for library functions. AC_FUNC_CHOWN AC_FUNC_FORK AC_FUNC_MMAP AC_FUNC_MALLOC AC_FUNC_REALLOC AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK AC_FUNC_WAIT3 AC_CHECK_FUNCS([alarm atexit atoll backtrace clock_gettime daemon dup2 gai_strerror getcwd getaddrinfo gethostbyname gethostname getnameinfo getpass gettimeofday hstrerror inet_ntoa isascii kqueue malloc memmove memset mkdir mmap munmap poll realpath select setenv setlogin setsid sigaction socket strcasecmp strchr strcspn strdup strerror strncasecmp strpbrk strrchr strspn strstr strtoul snprintf tzset waitpid]) # Check for command line options AC_ARG_ENABLE(builtin-icons, AS_HELP_STRING([--enable-builtin-icons], [Enable built-in icons for dir listings])) AC_ARG_ENABLE(public-html, AS_HELP_STRING([--enable-public-html], [Enable ~user/public_html in non-chrooted setups])) AC_ARG_ENABLE(htaccess, AS_HELP_STRING([--enable-htaccess], [Enable .htaccess files for access control])) AC_ARG_ENABLE(htpasswd, AS_HELP_STRING([--enable-htpasswd], [Enable .htpasswd files for authentication])) AC_ARG_ENABLE(msie-padding, AS_HELP_STRING([--enable-msie-padding], [Add padding to error messages for Internet Explorer])) AC_ARG_ENABLE(dirlisting, AS_HELP_STRING([--disable-dirlisting], [Disable directory listings when no index file is found]),,[ enable_dirlisting=yes]) AC_ARG_WITH([systemd], [AS_HELP_STRING([--with-systemd=DIR], [Directory for systemd service files, default: auto])],, [with_systemd=auto]) AC_ARG_WITH([config], AS_HELP_STRING([--without-config], [Disable /etc/merecat.conf support using libConfuse]),, [with_config=yes]) AC_ARG_WITH(ssl, AS_HELP_STRING([--without-ssl], [Disable HTTPS support, default: enabled]),, [with_ssl=yes]) AC_ARG_WITH([symlinks], AS_HELP_STRING([--without-symlinks], [Disable httpd and in.httpd symlinks to merecat]),, [with_symlinks=yes]) AC_ARG_WITH([zlib], AS_HELP_STRING([--without-zlib], [Disable mod_deflate (gzip) using zlib]),, [with_zlib=auto]) AS_IF([test "x$enable_builtin_icons" = "xyes"], [ AC_DEFINE(BUILTIN_ICONS, [1], [Enables built-in icons for dir listings])]) AM_CONDITIONAL([HAVE_ICONS], [test "x$enable_builtin_icons" != "xyes"]) AS_IF([test "x$enable_public_html" = "xyes"], [ AC_DEFINE_UNQUOTED(TILDE_MAP_2, "public_html", [Enables ~user/public_html])]) AS_IF([test "x$enable_htaccess" = "xyes"], [ AC_DEFINE_UNQUOTED(ACCESS_FILE, ".htaccess", [Enables .htaccess access control files])]) AS_IF([test "x$enable_htpasswd" = "xyes"], [ AC_DEFINE_UNQUOTED(AUTH_FILE, ".htpasswd", [Enables .htpasswd auth. files])]) AM_CONDITIONAL([ENABLE_HTPASSWD], [test "x$enable_htpasswd" = "xyes"]) AS_IF([test "x$enable_msie_padding" = "xyes"], [ AC_DEFINE(MSIE_PADDING, [1], [Enable padding of error messages for IE])]) AS_IF([test "x$enable_dirlisting" != "xno"], [ AC_DEFINE(GENERATE_INDEXES, [1], [Automatically generate directory index when index file is missing])]) AC_ARG_VAR(WEBDIR, [Document root of your web server [LOCALSTATEDIR/www]]) AS_IF([test "x${WEBDIR}" = "x"], [ WEBDIR=$localstatedir/www]) AS_IF([test "x$with_config" != "xno"], [ AC_DEFINE([HAVE_LIBCONFUSE], [1], [Build with support for /etc/merecat.conf]) PKG_CHECK_MODULES([confuse], [libconfuse >= 2.7])]) AM_CONDITIONAL([HAVE_CONFUSE], [test "x$with_config" != "xno"]) AM_CONDITIONAL([CREATE_SYMLINKS], [test "x$with_symlinks" != "xno"]) AS_IF([test "x$with_ssl" != "xno"], [ PKG_CHECK_MODULES([OpenSSL], [openssl >= 1.1.1]) LDFLAGS="$LDFLAGS $OpenSSL_LIBS" CPPFLAGS="$CPPFLAGS $OpenSSL_CFLAGS" AC_CHECK_LIB([crypto], [EVP_EncryptInit], [], AC_MSG_ERROR([*** Crypto library (OpenSSL) not found!])) AC_CHECK_LIB([ssl], [SSL_library_init], [], AC_CHECK_LIB([ssl], [OPENSSL_init_ssl], [], AC_MSG_ERROR([*** SSL library (OpenSSL) not found!]))) AC_CHECK_HEADERS([openssl/crypto.h openssl/x509.h openssl/pem.h openssl/ssl.h openssl/tls1.h openssl/err.h], [], AC_MSG_ERROR([*** Cannot find required header files!]), [ #include ]) AC_CHECK_DECLS([SSL_COMP_free_compression_methods,SSL_CTX_set_ecdh_auto], [], [], [ #ifdef HAVE_OPENSSL_ERR_H #include #endif #ifdef HAVE_OPENSSL_RAND_H #include #endif #ifdef HAVE_OPENSSL_CONF_H #include #endif #ifdef HAVE_OPENSSL_ENGINE_H #include #endif #include #include ]) AC_DEFINE([ENABLE_SSL], [1], [Enable HTTPS support]) ]) AM_CONDITIONAL([ENABLE_SSL], test "x$with_ssl" = "xyes") AS_IF([test "x$with_zlib" != "xno"], [ AC_CHECK_HEADERS([zlib.h]) AS_IF([test "x$ac_cv_header_zlib_h" = "xyes" -o "x$with_zlib" = "xyes"], [ PKG_CHECK_MODULES([zlib], [zlib >= 1.2.3.4])]) ]) # Check where to install the systemd .service file AS_IF([test "x$with_systemd" = "xyes" -o "x$with_systemd" = "xauto"], [ def_systemd=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) AS_IF([test "x$def_systemd" = "x"], [AS_IF([test "x$with_systemd" = "xyes"], [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) with_systemd=no], [with_systemd="$def_systemd"])] ) AS_IF([test "x$with_systemd" != "xno"], [AC_SUBST([systemddir], [$with_systemd])]) AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemd" != "xno"]) # Expand $sbindir early, into $SBINDIR, for systemd unit file # NOTE: This does *not* take prefix/exec_prefix override at "make # install" into account, unfortunately. test "x$prefix" = xNONE && prefix= test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DOCDIR=`eval echo $docdir` DOCDIR=`eval echo $DOCDIR` AC_SUBST(DOCDIR) SBINDIR=`eval echo $sbindir` SBINDIR=`eval echo $SBINDIR` AC_SUBST(SBINDIR) WWWDIR=`eval echo $WEBDIR` AC_SUBST(WWWDIR) AC_OUTPUT merecat-2.31+git20220513+ds/doc/000077500000000000000000000000001424066547300156375ustar00rootroot00000000000000merecat-2.31+git20220513+ds/doc/README.thttpd000066400000000000000000000024561424066547300200340ustar00rootroot00000000000000 thttpd - tiny/turbo/throttling HTTP server version 2.25b of 29dec2003 thttpd is a simple, small, portable, fast, and secure HTTP server. Simple: It handles only the minimum necessary to implement HTTP/1.1. Small: See the size comparison chart at http://www.acme.com/software/thttpd/notes.html#sizes. It also has a very small run-time size, since it does not fork and is very careful about memory allocation. Portable: It compiles cleanly on FreeBSD 2.x/3.x, SunOS 4.1.x, Solaris 2.x, BSD/OS 2.x, Linux 1.2.x, OSF/1 (on a 64-bit Alpha), and no doubt many others. Fast: In typical use it's about as fast as the best full-featured servers (Apache, NCSA, Netscape). Under extreme load it's much faster. Secure: It goes to great lengths to protect the web server machine against attacks and breakins from other sites. It also has one extremely useful feature (URL-traffic-based throttling) that no other server currently has. See the manual entry for more details. See the INSTALL file for configuration and installation instructions. Check the web page (http://www.acme.com/software/thttpd/) for updates, or add yourself to the mailing list by sending a "subscribe" to thttpd-announce-request@mail.acme.com. Comments to: Jef Poskanzer jef@mail.acme.com http://www.acme.com/jef/ merecat-2.31+git20220513+ds/doc/TODO000066400000000000000000000067271424066547300163430ustar00rootroot00000000000000- - - - - - - - - - before release - - - - - - - - - - - Add 'server' section to config file, server title { host = virual-host.name port = PORT ssl = BOOL redirect = site.tla:port } - Let 'server' directives slowly replace vhost matching When virtual-host=false we can use either the title of the server directive or a separate host/server-name setting for matching the incoming requests. - Add 'location' section to config file This to be able to set max-age per PATTERN rather than for all files, which may otherwise mess up reloading, e.g. a blog. location "PATTERN" { max-age = 2d add-header { key = "Cache-Control" value = "public, no-transform" } gzip = true } - Add proxy-pass to location directive - Let command line options override port/host etc. that are in the global section of the config file. - Verify cert at startup, abort if cert is too old or new add command line option to override (embedded systems) - - - - - - - - - - high priority - - - - - - - - - - Look into compressing CGI and ls() output using fmemopen() IPv6 not working right. Problem with ACME News downloads. PATH_INFO interferes with the authorization. Why is the client's IP address showing up in paths? Fetches with numeric IP addresses and no Host: header are screwing up the vhost code? 143.90.193.229 - - [06/Apr/2000:09:21:34 -0700] "GET /209.133.38.22/software/thttpd/ HTTP/1.0" 200 12093 "http://www.dbphotography.demon.co.uk/index.html" "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)" 143.90.193.229 - - [06/Apr/2000:09:21:37 -0700] "GET /143.90.193.229/software/thttpd/anvil_thttpd.gif HTTP/1.0" 403 - "http://www.acme.com/software/thttpd/" "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)" Add comment on INDEX_NAMES that it should be simple filenames only. The error page generated for non-local referers should include the original URL as an active link. Make open in mmc.c use O_NONBLOCK flag, to prevent DOS attack via a named pipe? - - - - - - - - - - later - - - - - - - - - - Document how symlinks interact with .htpasswd - authorization is checked on the result of the symlink, and not the origin. Change redirect to put the Refresh command in the HTTP headers, instead of a META tag. Add TCP_NODELAY, but after CGIs get spawned. Add stat cache? 1 minute expiry? Ifdef the un-close-on-exec CGI thing for Linux only. Add keep-alives, via a new state in thttpd.c. - - - - - - - - - - someday - - - - - - - - - - The special world-permissions checking is probably bogus. For one thing, it doesn't handle restrictive permissions on parent directories properly. It should probably just go away. redirect should interpret a path with a trailing / as /index.html ssi should change $cwd to the source document's location. Allow .throttle files in individual directories. Log-digesting scripts. Config web page. Common errors: Not realizing that -c overrides CGI_PATTERN instead of augmenting it. Using a directory name for the -c pattern. - - - - - - - - - - 3.x - - - - - - - - - - Tasklets re-write. - - - - - - - - - - general - - - - - - - - - - Release process: - update version number in version.h README INSTALL and contrib/redhat-rpm/thttpd.spec - do a tdiff and update the local installation - do an rcstreeinfo, and check in all files - make tar - mv it to .. - update version number in ../thttpd.html - update ~acmeweb/updates.html - mail announcement to thttpd-announce merecat-2.31+git20220513+ds/doc/cgi.txt000066400000000000000000000116761424066547300171550ustar00rootroot00000000000000 -*-org-*- * Supported CGI environment variables | | Key | RFC | Value | |-----+----------------------+-----+--------------------------------------------------------------------------------| | [X] | AUTH_TYPE | YES | Basic supported | | [X] | CONTENT_LENGTH | YES | | | [X] | CONTENT_TYPE | YES | | | [ ] | DOCUMENT_ROOT | | The root directory of your server | | [X] | GATEWAY_INTERFACE | YES | "CGI/1.1" | | [ ] | HTTP_ACCEPT | | | | [ ] | HTTP_ACCEPT_CHARSET | | | | [ ] | HTTP_ACCEPT_ENCODING | | | | [ ] | HTTP_ACCEPT_LANGUAGE | | | | [X] | HTTP_AUTHORIZATION | | Workaround for Apache & php5-cgi, see AUTH_TYPE | | [ ] | HTTP_CONNECTION | | | | [ ] | HTTP_COOKIE | | The visitor's cookie, if one is set | | [X] | HTTP_HOST | | The hostname of the page being attempted | | [ ] | HTTP_KEEP_ALIVE | | | | [X] | HTTP_REFERER | | The URL of the page that called your program | | [X] | HTTP_USER_AGENT | | The browser type of the visitor | | [ ] | HTTPS | | "on" if the program is being called through a secure server | | [ ] | PATH | | The system path your server is running under | | [X] | PATH_INFO | YES | | | [X] | PATH_TRANSLATED | YES | | | [X] | QUERY_STRING | YES | The query string (see GET, below) | | [X] | REDIRECT_STATUS | | Non-standard environment varialbe used by PHP | | [X] | REMOTE_ADDR | YES | The IP address of the visitor | | [X] | REMOTE_HOST | YES | The hostname of the visitor[fn:1] | | [ ] | REMOTE_IDENT | MAY | May be used to provide RFC 1413 connection identity information | | [ ] | REMOTE_PORT | | The port from which the request was sent | | [X] | REMOTE_USER | YES | The visitor's username (for .htaccess-protected pages) | | [X] | REQUEST_METHOD | YES | GET or POST | | [X] | REQUEST_URI | | Interpreted pathname of requested document/CGI (relative to the document root) | | [X] | SCRIPT_FILENAME | | Full pathname of the current CGI, used by PHP | | [X] | SCRIPT_NAME | YES | Interpreted pathname of the current CGI (relative to the document root) | | [ ] | SERVER_ADDR | | Server IP address | | [ ] | SERVER_ADMIN | | The email address for your server's webmaster | | [X] | SERVER_NAME | YES | Your server's fully qualified domain name (e.g. www.cgi101.com) | | [X] | SERVER_PORT | YES | The port number your server is listening on | | [X] | SERVER_PROTOCOL | YES | "HTTP/1.1" | | [ ] | SERVER_SIGNATURE | | | | [X] | SERVER_SOFTWARE | YES | The server software you're using, e.g. "Apache/1.3 (Win32)" | The RFC column refers to mandatory CGI environment variables, as defined in . ---- [fn:1] If your server has reverse-name-lookups on; otherwise this is the IP address again. merecat-2.31+git20220513+ds/lib/000077500000000000000000000000001424066547300156405ustar00rootroot00000000000000merecat-2.31+git20220513+ds/lib/.gitignore000066400000000000000000000000201424066547300176200ustar00rootroot00000000000000.deps .dirstamp merecat-2.31+git20220513+ds/lib/lstat.c000066400000000000000000000031361424066547300171360ustar00rootroot00000000000000/* lstat.c - lstat(2) replacement, requires POSIX fstatat(2) ** ** Copyright (C) 2019-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include int lstat(const char *pathname, struct stat *statbuf) { return fstatat(AT_FDCWD, pathname, statbuf, AT_SYMLINK_NOFOLLOW); } merecat-2.31+git20220513+ds/lib/malloc.c000066400000000000000000000004301424066547300172500ustar00rootroot00000000000000#if HAVE_CONFIG_H # include #endif #undef malloc #include void *malloc (); /* * Allocate an N-byte block of memory from the heap. * If N is zero, allocate a 1-byte block. */ void *rpl_malloc (size_t n) { if (n == 0) n = 1; return malloc (n); } merecat-2.31+git20220513+ds/lib/realloc.c000066400000000000000000000005101424066547300174210ustar00rootroot00000000000000#if HAVE_CONFIG_H # include #endif #undef realloc #undef malloc #include #include void * rpl_realloc (void *p, size_t n) { void *result; if (n == 0) n = 1; if (p == NULL) result = malloc (n); else result = realloc (p, n); if (result == NULL) errno = ENOMEM; return result; } merecat-2.31+git20220513+ds/lib/strlcat.c000066400000000000000000000033221424066547300174600ustar00rootroot00000000000000/* $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $ */ /* * Copyright (c) 1998, 2015 Todd C. Miller * * 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. */ #include #include /* * Appends src to string dst of size dsize (unlike strncat, dsize is the * full size of dst, not space left). At most dsize-1 characters * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). * Returns strlen(src) + MIN(dsize, strlen(initial dst)). * If retval >= dsize, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t dsize) { const char *odst = dst; const char *osrc = src; size_t n = dsize; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end. */ while (n-- != 0 && *dst != '\0') dst++; dlen = dst - odst; n = dsize - dlen; if (n-- == 0) return(dlen + strlen(src)); while (*src != '\0') { if (n != 0) { *dst++ = *src; n--; } src++; } *dst = '\0'; return(dlen + (src - osrc)); /* count does not include NUL */ } merecat-2.31+git20220513+ds/lib/strlcpy.c000066400000000000000000000030771424066547300175130ustar00rootroot00000000000000/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ /* * Copyright (c) 1998, 2015 Todd C. Miller * * 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. */ #include #include /* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; /* Copy as many bytes as will fit. */ if (nleft != 0) { while (--nleft != 0) { if ((*dst++ = *src++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) *dst = '\0'; /* NUL-terminate dst */ while (*src++) ; } return(src - osrc - 1); /* count does not include NUL */ } merecat-2.31+git20220513+ds/lib/tempfile.c000066400000000000000000000044061424066547300176150ustar00rootroot00000000000000/* A secure tmpfile() replacement. * * Copyright (c) 2015-2021 Joachim Wiberg * * Permission to use, copy, modify, and/or 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. */ #define _GNU_SOURCE #include #include #include /* O_TMPFILE requires -D_GNU_SOURCE */ #include /* fdopen() */ #include /* mkostemp() */ #include /* umask() */ static FILE *fallback(void) { char nm[15] = _PATH_TMP "XXXXXXXX"; int fd; fd = mkostemp(nm, O_CLOEXEC); if (-1 == fd) return NULL; return fdopen(fd, "w+"); } /** * A secure tmpfile() replacement * * This is the secure replacement for tmpfile() that does not exist in * GLIBC. It uses the Linux specific @c O_TMPFILE and @c O_EXCL to hide * the filename. When the @c FILE is fclose()'ed, the file contents is * lost. The file is hidden in the @c _PATH_TMP ("/tmp") directory in * the system. * * This function requires Linux 3.11, or later, due to @c O_TMPFILE. * Not all file systems support hidden inodes, in which case this * function defaults to call tmpfile() as a fallback. * * @returns An open @c FILE pointer, or @c NULL on error. */ FILE *tempfile(void) { #ifdef O_TMPFILE /* Only on Linux, with fairly recent (G)LIBC */ int fd; mode_t oldmask; oldmask = umask(0077); fd = open(_PATH_TMP, O_TMPFILE | O_RDWR | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR); umask(oldmask); if (-1 == fd) { if (errno == EOPNOTSUPP) return fallback(); return NULL; } return fdopen(fd, "w+"); #else return fallback(); #endif } /** * Local Variables: * indent-tabs-mode: t * c-file-style: "linux" * End: */ merecat-2.31+git20220513+ds/man.sh000077500000000000000000000007371424066547300162130ustar00rootroot00000000000000#!/bin/sh -e # Generates HTML versions of man pages using mandoc GEN=`which mandoc` TOP=`git rev-parse --show-toplevel` HEAD=$TOP/www/header.html FOOT=$TOP/www/footer.html for file in `ls $TOP/man/*.[158]`; do name=`basename $file` web=$TOP/www/$name.html echo "Updating $web ..." cat $HEAD > $web mandoc -T html -O fragment $file >> $web cat $FOOT >> $web sed -i "s/%TITLE%/$name/" $web done merecat-2.31+git20220513+ds/man/000077500000000000000000000000001424066547300156455ustar00rootroot00000000000000merecat-2.31+git20220513+ds/man/Makefile.am000066400000000000000000000007121424066547300177010ustar00rootroot00000000000000if ENABLE_HTPASSWD dist_man1_MANS = htpasswd.1 endif if HAVE_CONFUSE dist_man5_MANS = merecat.conf.5 endif dist_man8_MANS = merecat.8 ssi.8 # in.httpd.8 SYMLINK = httpd.8 # Hook in install merecat.8 --> in.httpd-8, httpd-8 symlinks if CREATE_SYMLINKS install-data-hook: @for file in $(SYMLINK); do \ link=$(DESTDIR)$(mandir)/man8/$$file; \ [ "`readlink $$link`" = "merecat.8" ] && continue; \ $(LN_S) merecat.8 $$link; \ done endif merecat-2.31+git20220513+ds/man/htpasswd.1000066400000000000000000000065461424066547300175770ustar00rootroot00000000000000.\" -*- nroff -*- .\" The Merecat web server stems from both sthttpd and thttpd, both of .\" which are free software under the 2-clause simplified BSD license. .\" .\" Copyright (c) 1995-2015 Jef Poskanzer .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" .\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE .\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR .\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF .\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN .\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF .\" THE POSSIBILITY OF SUCH DAMAGE. .Dd Aug 3, 2019 .Dt htpasswd 1 .Os "merecat (2.32)" .Sh NAME .Nm htpasswd .Nd Create and update user authentication files .Sh SYNOPSIS .Nm .Op Fl cm .Ar FILE .Ar USERNAME .Sh DESCRIPTION .Nm is used to create and update the flat-files used to store usernames and password for basic authentication of HTTP users. Resources available from the Merecat web server can be restricted to just the users listed in the files created by htpasswd. This program can only be used when the usernames are stored in a flat-file. .Pp This manual page only lists the command line arguments for .Nm . For details of the directives necessary to enable user authentication in the web server, see .Xr merecat 8 , the Merecat httpd README, or the GitHub project home page .Aq https://github.com/troglobit/merecat . .Sh OPTIONS .Bl -tag -width Ds .It Fl c Create, or recreate the .htpwassd file, .Ar FILE , if it already exists. .It Fl m Use MD5 encryption for passwords, this is tested at runtime. So this option is simply for compatibility with other similar tools. .It Ar FILE Name of the file to contain the user name and password, usually .htpasswd. If .Fl c is given, this file is created if it does not already exist, or deleted and recreated if it does exist. .It Ar USERNAME The username to create or update in passwdfile, .Ar FILE . If username does not exist is this file, an entry is added. If it does exist, the password is changed. .El .Sh SEE ALSO .Xr merecat 8 .Sh AUTHORS .An Rob McCool Aq robm@stanford.edu originally wrote .Nm for NCSA httpd. It then (naturally) made its way to Apache and other web servers, like Roxen Challenger. .An Jef Poskanzer Aq jef@mail.acme.com modified it 29aug97 to accept new password on stdin, if stdin is a pipe or file. This is necessary for use from CGI. merecat-2.31+git20220513+ds/man/merecat.8000066400000000000000000000636101424066547300173640ustar00rootroot00000000000000.\" -*- nroff -*- .\" The Merecat web server stems from both sthttpd and thttpd, both of .\" which are free software under the 2-clause simplified BSD license. .\" .\" Copyright (c) 1995-2015 Jef Poskanzer .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" .\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE .\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR .\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF .\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN .\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF .\" THE POSSIBILITY OF SUCH DAMAGE. .Dd Nov 28, 2021 .Dt MERECAT 8 SMM .Os "merecat (2.32)" .Sh NAME .Nm merecat .Nd Simple, small and fast HTTP server .Sh SYNOPSIS .Nm .Op Fl ghnrsSvV .Op Fl c Ar CGI .Op Fl d Ar PATH .Op Fl f Ar FILE .Op Fl I Ar IDENT .Op Fl l Ar LEVEL .Op Fl p Ar PORT .Op Fl P Ar PIDFN .Op Fl t Ar FILE .Op Fl u Ar USER .Op Ar WEBDIR .Op Ar HOSTNAME .Sh DESCRIPTION .Nm started out as a pun at Mongoose, which is another great web server, but is now useful for actual web serving purposes. It is however not a real Meerkat, merely yet another copycat, forked from the great .Nm thttpd created by Jef Poskanzer. .Pp The limited feature set makes Merecat very quick: .Pp .Bl -dash -offset indent -compact .It Virtual hosts .It URL-traffic-based throttling .It CGI/1.1 .It HTTP/1.1 Keep-alive .It Built-in gzip deflate using zlib .It HTTPS support using OpenSSL/LibreSSL .It Dual server support, both HTTP/HTTPS from one process .It HTTP redirect support, per server. E.g., possible to redirect from HTTP to HTTPS. Limited set of Nginx style environment variables supported. .It Native PHP support, using php-cgi, if enabled in .Xr merecat.conf 5 .El .Pp The resulting footprint (~140 kiB) makes Merecat suitable for small and embedded systems. .Sh OPTIONS This program follows the usual UNIX command line syntax. Some options are, however, not available when .Nm is built with support for .Pa /etc/merecat.conf . The distributed archive comes with an example configuration file, which should be fairly straightforward to comprehend. For details on the available configuration directives, see .Xr merecat.conf 5 . .Pp The options, in their entirety, are as follows: .Bl -tag -width Ds .It Fl c Ar CGI Wildcard pattern for CGI programs. The config file setting for this flag is .Cm cgi-pattern = Ar PATTERN . The default is .Qq Ar **.cgi|/cgi-bin/* . .Pp For more details, see below. .It Fl d Ar PATH Directory to chdir() to after chrooting. If you are not chrooting use the .Ar WEBDIR to do a single chdir(). If you are chrooting, this lets you put the web files in a subdirectory of the chroot tree, instead of in the top level mixed in with the chroot files. The config file setting for this flag is .Cm data-directory = Ar DIR . .It Fl f Ar FILE The config file to read. By default .Nm looks for .Pa /etc/merecat.conf , unless the software has been configured to use a different prefix. .Pp If the default system coonfiguration file is missing, or if .Nm is started with .Fl f Ar none , .Nm will run in stand-alone mode using only command line options like listening port and server root directory. .It Fl g Use global .Pa .htpasswd and .Pa .htaccess files. This means that every file in the entire document tree is protected by a single .Pa .htpasswd or .Pa .htaccess file at the top of the tree. Otherwise the semantics of the .Pa .htpasswd and .Pa .htaccess files are the same. If this option is set but there is no .Pa .htpasswd or .Pa .htaccess files in the top-level directory, then .Nm proceeds as if the option was not set \(em first looking for local .Pa .htpasswd and .Pa .htaccess files, and if they do not exist either then serving the requested file without any password or access restriction. .Pp The config file setting for this flag is .Cm global-passwd = Ar . .It Fl h Show summary of command line options and exit. .It Fl I Ar IDENT The .Xr syslog 3 identity to use for all log messages. Useful when running multiple servers. Defaults to use the program name, i.e. .Qq Nm . .It Fl l Ar LEVEL Set log level: none, err, info, .Ar notice , debug .It Fl n Runs .Nm in the foreground like a regular program. Required when when running in a process monitor like Finit or systemd. This also enables logging of errors and warnings to stderr, which can be disabled with .Fl s . .It Fl p Ar PORT Alternate TCP port number to listen on. The default is 80. The config file setting for this flag is .Cm port = Ar PORT . .It Fl P Ar PIDFN Optional PID file name. By default the .Ar IDENT option, or its default value, is used to construct the PID file name. Usually this results in .Pa /var/run/merecat.pid . If the argument to this option is an absolute path it will be used as-is. Otherwise the argument will be used as the basename for the PID file. .It Fl r Do a chroot() at initialization time, restricting file access to the program's current directory. The config file setting for this flag is .Cm chroot = Ar . .It Fl s Use syslog, even though running in foreground, .Fl n . .Nm uses syslog by default, this option is only relevant when running in the foreground to prevent warning and error messages to be printed to stderr. .It Fl S Do explicit symbolic link checking. Normally, merecat does not expand any symbolic links in filenames. For increased security this option can be enabled to check that the resulting path stays within the original document tree. Note, that if you are using the chroot option, the symlink checking is unnecessary and is turned off, so the safe way to save those CPU cycles is to use chroot. The config file setting for this is .Cm check-symlinks = Ar . .It Fl t Ar FILE Enable throttling using this file with throttle settings. See below for details. .It Fl u Ar USERNAME User to drop privileges to to after initialization when started as root. The default is .Ar nobody , on some systems .Ar www-data is preferred. The config file setting for this flag is .Cm username = Ar USER . .It Fl v Do el-cheapo virtual hosting. The config file setting for this flag is .Cm virtual-host = Ar . .It Fl V Shows the current version info. .It Ar WEBDIR This optional argument is provided as a convenience \(em by default .Nm serves files from the current directory. The config file setting for this is .Cm directory = Ar DIR . .It Ar HOSTNAME A second optional command line argument can be given to specify the hostname to bind to, for multihoming. The default is to bind to all hostnames supported on the local machine. See below for details. The config file setting for this flag is .Cm hostname = Ar HOSTNAME . .El .Sh CHROOT chroot() is a system call that restricts the program's view of the filesystem to the current directory and directories below it. It becomes impossible for remote users to access any file outside of the initial directory. The restriction is inherited by child processes, so CGI programs get it too. This is a very strong security measure, and is recommended. The only downside is that only root can call chroot(), so this means the program must be started as root. However, the last thing it does during initialization is to give up root access by becoming another user, so this is safe. .Pp The program can also be compile-time configured to always do a chroot(), without needing the -r flag. .Pp Note that with some other web servers, such as NCSA httpd, setting up a directory tree for use with chroot() is complicated, involving creating a bunch of special directories and copying in various files. With merecat it's a lot easier, all you have to do is make sure any shells, utilities, and config files used by your CGI programs and scripts are available. If you have CGI disabled, or if you make a policy that all CGI programs must be written in a compiled language such as C and statically linked, then you probably don't have to do any setup at all. .Pp However, one thing you should do is tell syslogd about the chroot tree, so that merecat can still generate syslog messages. Check your system's syslogd man page for how to do this. In FreeBSD you would put something like this in .Pa /etc/rc.conf : .Bd -unfilled -offset left syslogd_flags="-l /usr/local/www/data/dev/log" .Ed Substitute in your own chroot tree's pathname, of course. Don't worry about creating the log socket, syslogd wants to do that itself. (You may need to create the dev directory.) In Linux the flag is -a instead of -l, and there may be other differences. .Sh CGI .Pp Merecat httpd supports the CGI 1.1 spec., .Lk https://tools.ietf.org/html/rfc3875 . .Pp In order for a CGI program to be allowed to run, its name must match the pattern specified either at compile time, on the command line, or in the config file. This is a simple shell-style filename pattern. Use * to match any string not including a slash, or ** to match any string including slashes, or ? to match any single character. Multiple patterns separated by | can also be used. The patterns get checked against the filename part of the incoming URL. Remember to quote any wildcard characters so that the shell doesn't mess with them. .Pp Restricting CGI programs to a single directory lets the site admin review them for security holes, and is strongly recommended. If there are individual users that you trust, you can enable their directories too using the pipe syntax, e.g. "|/jef/**". .Pp To disable CGI as a security measure, either disable the default .Cm CGI_PATTERN in .Pa merecat.h , or set the configuration file option to the empty string, like this: .Cm cgi-pattern = Qq .Pp Note: the current working directory when a CGI program gets run is the directory that the CGI program lives in. This isn't in the CGI 1.1 spec, but it's what most other HTTP servers do. .Pp Relevant .Pa merecat.h defines: .Cm CGI_PATTERN, CGI_TIMELIMIT, CGI_NICE, CGI_PATH, CGI_LD_LIBRARY_PATH, CGIBINDIR . .Sh BASIC AUTHENTICATION Basic authentication is available as an option at compile time. See the included configure script for details. When enabled, it uses a password file in the directory to be protected, called .Pa .htpasswd by default. This file is formatted as the familiar colon-separated username/encrypted-password pair, records delimited by newlines. The utility program .Xr htpasswd 1 is included to help create and modify .Pa .htpasswd files. .Pp .Nm can use a global .Pa .htpasswd file if started with the .Fl g switch, or you can rely on a per directory file which also protects subdirectories. .Pp Relevant .Pa merecat.h define: .Cm AUTH_FILE .Sh ACCESS RESTRICTION Access restriction is available as an option at compile time. If enabled, it uses an access file in the directory to be protected, called .Pa .htaccess by default. This file consists of a rule and a host address or a network range per line. Valid rules are: .Bl -tag -width Ds .It Cm allow from The following host address or network range is allowed to access the requested directory and its files. .It Cm deny from The following host address or network range is not allowed to access the requested directory and its files. .El .Pp There are three ways to specify a valid host address or network range: .Bl -tag -width Ds .It Cm IPv4 host address , e.g. 10.2.3.4 .It Cm IPv4 network with subnet mask , e.g. 10.0.0.0/255.255.0.0 .It Cm IPv4 network using CIDR notation , e.g. 10.0.0.0/16 .El .Pp .Nm can use a global .Pa .htaccess file if started with the .Fl g switch, or you can rely on a per directory file which also protects subdirectories. .Pp Note that rules are processed in the same order as they are listed in the access file and that the first rule which matches the client's address is applied (there is no order clause). .Pp So if there is no allow from 0.0.0.0/0 at the end of the file the default action is to deny access. .Pp Relevant .Pa merecat.h define: .Cm ACCESS_FILE .Sh THROTTLING .Pp The throttle file lets you set maximum byte rates on URLs or URL groups. You can optionally set a minimum rate too. The format of the throttle file is very simple. A # starts a comment, and the rest of the line is ignored. Blank lines are ignored. The rest of the lines should consist of a pattern, whitespace, and a number. The pattern is a simple shell-style filename pattern, using ?/**/*, or multiple such patterns separated by |. .Pp The numbers in the file are byte rates, specified in units of bytes per second. For comparison, a v.90 modem gives about 5000 B/s depending on compression, a double-B-channel ISDN line about 12800 B/s, and a T1 line is about 150000 B/s. If you want to set a minimum rate as well, use number-number. .Pp Example: .Bd -unfilled -offset left # throttle file for www.acme.com ** 2000-100000 # limit total web usage to 2/3 of our T1, # but never go below 2000 B/s **.jpg|**.gif 50000 # limit images to 1/3 of our T1 **.mpg 20000 # and movies to even less jef/** 20000 # jef's pages are too popular .Ed .Pp Throttling is implemented by checking each incoming URL filename against all of the patterns in the throttle file. The server accumulates statistics on how much bandwidth each pattern has accounted for recently (via a rolling average). If a URL matches a pattern that has been exceeding its specified limit, then the data returned is actually slowed down, with pauses between each block. If that's not possible (e.g. for CGI programs) or if the bandwidth has gotten way larger than the limit, then the server returns a special code saying .Qq try again later . .Pp The minimum rates are implemented similarly. If too many people are trying to fetch something at the same time, throttling may slow down each connection so much that it's not really useable. Furthermore, all those slow connections clog up the server, using up file handles and connection slots. Setting a minimum rate says that past a certain point you should not even bother \(em the server returns the .Qq try again later code and the connection is not even started. .Pp There is no provision for setting a maximum connections/second throttle, because throttling a request uses as much CPU as handling it, so there would be no point. There is also no provision for throttling the number of simultaneous connections on a per-URL basis. However you can control the overall number of connections for the whole server very simply, by setting the operating system's per-process file descriptor limit before starting merecat. Be sure to set the hard limit, not the soft limit. .Sh MULTIHOMING Multihoming means using one machine to serve multiple hostnames. For instance, if you're an internet provider and you want to let all of your customers have customized web addresses, you might have www.joe.acme.com, www.jane.acme.com, and your own www.acme.com, all running on the same physical hardware. This feature is also known as virtual hosts. There are three steps to setting this up. .Pp One, make DNS entries for all of the hostnames. The current way to do this, allowed by HTTP/1.1, is to use CNAME aliases, like so: .Bd -unfilled -offset left www.acme.com IN A 192.100.66.1 www.joe.acme.com IN CNAME www.acme.com www.jane.acme.com IN CNAME www.acme.com .Ed .Pp However, this is incompatible with older HTTP/1.0 browsers. If you want to stay compatible, there is a different way - use A records instead, each with a different IP address, like so: .Bd -unfilled -offset left www.acme.com IN A 192.100.66.1 www.joe.acme.com IN A 192.100.66.200 www.jane.acme.com IN A 192.100.66.201 .Ed .Pp This is bad because it uses extra IP addresses, a somewhat scarce resource. But if you want people with older browsers to be able to visit your sites, you still have to do it this way. .Pp Step two. If you're using the modern CNAME method of multihoming, then you can skip this step. Otherwise, using the older multiple-IP-address method you must set up IP aliases or multiple interfaces for the extra addresses. You can use ifconfig(8)'s alias command to tell the machine to answer to all of the different IP addresses. Example: .Bd -unfilled -offset left ifconfig le0 www.acme.com ifconfig le0 www.joe.acme.com alias ifconfig le0 www.jane.acme.com alias .Ed .Pp If your OS's version of ifconfig doesn't have an alias command, you're probably out of luck (but see .Lk http://www.acme.com/software/thttpd/notes.html for more info). .Pp Third and last, you must set up merecat to handle the multiple hosts. The easiest way is with the .Fl v flag. This works with either CNAME multihosting or multiple-IP multihosting. What it does is send each incoming request to a subdirectory based on the hostname it's intended for. All you have to do in order to set things up is to create those subdirectories in the directory where merecat will run. With the example above, you'd do like so: .Bd -unfilled -offset left mkdir www.acme.com www.joe.acme.com www.jane.acme.com .Ed .Pp If you're using old-style multiple-IP multihosting, you should also create symbolic links from the numeric addresses to the names, like so: .Bd -unfilled -offset left ln -s www.acme.com 192.100.66.1 ln -s www.joe.acme.com 192.100.66.200 ln -s www.jane.acme.com 192.100.66.201 .Ed .Pp This lets the older HTTP/1.0 browsers find the right subdirectory. .Pp There is an optional alternate step three if you're using multiple-IP multihosting: run a separate merecat process for each hostname This gives you more flexibility, since you can run each of these processes in separate directories, with different throttle files, etc. Example: .Bd -unfilled -offset left merecat -r /usr/www www.acme.com merecat -r -u joe /usr/www/joe www.joe.acme.com merecat -r -u jane /usr/www/jane www.jane.acme.com .Ed .Pp Remember, this multiple-process method does not work with CNAME multihosting \(em for that, you must use a single merecat process with the .Fl v flag. .Sh CUSTOM ERRORS merecat lets you define your own custom error pages for the various HTTP errors. There is a separate file for each error number, all stored in one special directory. The directory name is .Pa errors/ , at the top of the web directory tree. The error files should be named .Pa errNNN.html , where NNN is the error number. So for example, to make a custom error page for the authentication failure error, which is number 401, you would put your HTML into the file .Pa errors/err401.html . If no custom error file is found for a given error number, then the usual built-in error page is generated. .Pp In a virtual hosts setup you can also have different custom error pages for each host. In this case you put another .Pa errors/ directory in the top of that virtual host's web tree. .Nm will look first in the virtual host errors directory, and then in the server-wide errors directory, and if neither of those has an appropriate error file then it will generate the built-in error. .Sh NON-LOCAL REFERERS Sometimes another site on the net will embed your image files in their HTML files, which basically means they're stealing your bandwidth. You can prevent them from doing this by using non-local referer filtering. With this option, certain files can only be fetched via a local referer. The files have to be referenced by a local web page. If a web page on some other site references the files, that fetch will be blocked. There are three config file variables for this feature: .Bl -tag -width Ds .It Cm url-pattern = Qq Ar **.jpg|**.gif|**.au|**.wav A wildcard pattern for the URLs that should require a local referer. This is typically just image files, sound files, and so on. For example: .Bd -unfilled -offset left urlpat = "**.jpg|**.gif|**.au|**.wav" .Ed .Pp For most sites, that one setting is all you need to enable referer filtering. .It Cm check-referer = Ar By default, requests with no referer at all, or a null referer, or a referer with no apparent hostname, are allowed. With this variable set, such requests are disallowed. .It Cm local-pattern = Qq Ar PATTERN A wildcard pattern that specifies the local host or hosts. This is used to determine if the host in the referer is local or not. If not specified it defaults to the actual local hostname. .El .Sh SYMLINKS .Nm is very picky about symbolic links. Before delivering any file, it first checks each element in the path to see if it is a symbolic link, and expands them all out to get the final actual filename. .Pp Along the way it checks for things like links with .Qq .. that go above the server's directory, and absolute symlinks (ones that start with a /). These are prohibited as security holes, so the server returns an error page for them. .Pp This means you cannot set up your web directory with a bunch of symlinks pointing to individual users' home web directories. Instead you do it the other way around \(em the user web directories are real subdirectories of the main web directory, and in each user's home directory there is a symlink pointing to their actual web directory. .Pp The CGI pattern is also affected \(em it gets matched against the fully-expanded filename. So, if you have a single CGI directory but then put a symbolic link in it pointing somewhere else, that will not work. The CGI program will be treated as a regular file and returned to the client, instead of getting run. This could be confusing. .Sh PERMISSIONS .Nm is also picky about file permissions. It wants data files (HTML, images) to be world readable. Readable by the group that the merecat process runs as is not enough \(em .Nm checks explicitly for the world-readable bit. This is so that no one ever gets surprised by a file that's not set world-readable and yet somehow is readable by the HTTP server and therefore the *whole* world. .Pp The same logic applies to directories. As with the standard UNIX .Cm ls program, .Nm will only let you look at the contents of a directory if its read bit is on; but as with data files, this must be the world-read bit, not just the group-read bit. .Pp .Nm also wants the execute bit to be *off* for data files. A file that is marked executable but doesn't match the CGI pattern might be a script or program that got accidentally left in the wrong directory. Allowing people to fetch the contents of the file might be a security breach, so this is prohibited. Of course if an executable file *does* match the CGI pattern, then it just gets run as a CGI. .Pp In summary, data files should be mode 644 (rw-r--r--), directories should be 755 (rwxr-xr-x) if you want to allow indexing and 711 (rwx--x--x) to disallow it, and CGI programs should be mode 755 (rwxr-xr-x) or 711 (rwx--x--x). .Sh LOGS .Nm does all of its logging via .Xr syslog 3 . All log messages are prepended with the program name, unless the command line option .Fl I Ar IDENT is used. The facility defaults to .Ar LOG_DAEMON . Aside from error messages, there are only a few log entry types of interest, all fairly similar to CERN Common Log Format: .Bd -unfilled -offset left Aug 6 15:40:34 acme merecat[583]: 165.113.207.103 - - "GET /file" 200 357 Aug 6 15:40:43 acme merecat[583]: 165.113.207.103 - - "HEAD /file" 200 0 Aug 6 15:41:16 acme merecat[583]: referer http://www.acme.com/ -> /dir Aug 6 15:41:16 acme merecat[583]: user-agent Mozilla/1.1N .Ed .Pp Note that .Nm does not translate numeric IP addresses into domain names. This is both to save time and as a minor security measure (the numeric address is harder to spoof). .Pp If started in the foreground, .Fl n , and with debug log level, .Fl l Ar debug , logs will also be printed on stderr, unless the user also requested .Fl s . However, not all systems support the .Ar LOG_PERROR option to .Fn openlog . .Pp Relevant .Pa merecat.h define: .Cm LOG_FACILITY. .Sh SIGNALS .Nm handles a couple of signals, which you can send via the standard UNIX .Xr kill 1 command: .Bl -tag -width INT,TERM .It Cm INT,TERM These signals tell .Nm to shut down immediately. .It Cm USR1 This signal tells .Nm to toggle log level, between current log level and LOG_DEBUG. If .Nm was started with LOG_DEBUG the toggle will be to LOG_NOTICE, which is the default log level. .It Cm USR2 This signal tells .Nm to generate the statistics syslog messages immediately, instead of waiting for the regular hourly update. .El .Sh SEE ALSO .Xr merecat.conf 5 , .Xr ssi 8 , .Xr htpasswd 1 .Sh AUTHORS .An -split .An Jef Poskanzer Aq jef@mail.acme.com wrote the famous .Nm thttpd which .Nm is based on. .An Joachim Wiberg Aq troglobit@gmail.com introduced all new shiny bugs. .Sh THANKS .An -nosplit .Nm is a fork of .Nm sthttpd , which in turn is a fork of .Nm thttpd . So first and foremost, a huge thanks to .An Jef Poskanzer for creating .Nm thttpd and making it open source under the simplified 2-clause BSD license! .An Anthony G. Basile deserves another thank you, for merging Gentoo patches and refactoring the build system in .Nm sthttpd . .Pp Also, many thanks to contributors, reviewers, testers: John LoVerso, Jordan Hayes, Chris Torek, Jim Thompson, Barton Schaffer, Geoff Adams, Dan Kegel, John Hascall, Bennett Todd, KIKUCHI Takahiro, Catalin Ionescu, Anders Bornäs, and Martin Olsson. Special thanks to Craig Leres for substantial debugging and development during the early days of .Nm thttpd . merecat-2.31+git20220513+ds/man/merecat.conf.5000066400000000000000000000245231424066547300203050ustar00rootroot00000000000000.\" -*- nroff -*- .\" The Merecat web server stems from both sthttpd and thttpd, both of .\" which are free software under the 2-clause simplified BSD license. .\" .\" Copyright (c) 1995-2015 Jef Poskanzer .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" .\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE .\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR .\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF .\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN .\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF .\" THE POSSIBILITY OF SUCH DAMAGE. .Dd Nov 28, 2021 .Dt MERECAT.CONF 5 .Os "merecat (2.32)" .Sh NAME .Nm merecat.conf .Nd merecat httpd configuration file .Sh INTRODUCTION When .Nm merecat starts up it looks for its configuration file, .Pa /etc/merecat.conf . This manual page documents the settings available, which allows for more advanced setups. For simpler use-cases, however, you may not need a .Nm since the server runs fine with only command line parameters. .Pp .Sh DESCRIPTION The syntax of the config file is UNIX style .Cm key = value , separated by whitespace. The .Dq #\& character marks the start of a comment to end of line. The \\ character can be used as an escape character. .Pp .Em Note: changes to the configuration file are require a restart of .Nm merecat , unlike many other UNIX daemons .Cm SIGHUP does not reload the .Pa .conf file. .Ss Configuration Directives .Bl -tag -width Ds .It Cm charset = Qq Ar STRING Character set to use with text MIME types, default .Qq UTF-8 . If the default unicode charset causes trouble, try .Qq iso-8859-1 . .It Cm check-referer = Ar Enable check for external sites referencing material on your web server. For more information on referrers, see .Xr merecat 8 . Disabled by default. .It Cm check-symlinks = Ar For increased security, set this to true. Unless running chrooted in which case this is not really necessary. Disabled by default. .It Cm chroot = Ar Change web server root to .Ar WEBDIR , or the current directory, if no .Ar WEBDIR is given as argument. Chrooting is a security measure and means that .Nm cannot access files outside it, unless files are bind mounted, or similar into the chroot. Disabled by default. .It Cm compression-level = Ar -1..9 Control the compression level of the built-in Apache-like mod_deflate. The default value is -1, which gives a reasonable compromize between speed and compression. To disable compression set this to .Ar 0 and to get maximum compression, .Ar 9 . .Pp The default setting, .Ar -1 , means all "text/*" MIME type files, larger than 256 bytes, are compressed before sending to the client. .It Cm directory = Ar DIR If no WEBDIR is given on the command line this option can be used to change the web server document root. Defaults to the current directory. When chrooting this is the root directory, see the .Cm data-directory directive for more help. .It Cm data-directory = Ar DIR This setting is only relevant when chrooting, it adjusts the web server document root relative to the .Cm directory directive. .It Cm global-passwd = Ar Set this to true to protect the entire directory tree with a single .Pa .htpasswd and/or .Pa .htaccess file. When unset, which is the default, .Nm looks for a local .Pa .htpasswd and .Pa .htaccess file, or serves the file without password. .It Cm hostname = Ar HOSTNAME The hostname to bind to when multihoming. For more details on this, see below discussion. .It Cm list-dotfiles = Ar If dotfiles should be skipped in directory listings. Disabled by default. .It Cm local-pattern = Qq Ar PATTERN Used with .Cm check-referer , see .Xr merecat 8 for more details. .It Cm max-age = Ar SEC Controls the global max-age setting, in seconds, set in the HTTP/1.1 .Qq Ar Cache-Control: max-age header, returned with all responses. The default setting is disabled since v2.32 and the user is recommended to use per-resource cache control. See the server location directive for details. .It Cm port = Ar PORT The web server Internet port to listen to, defaults to 80, or 443 when HTTPS is enabled. See the .Cm ssl section below for more on configuring an HTTPS server. .It Cm url-pattern = Qq Ar PATTERN Used with .Cm check-referer , see .Xr merecat 8 for more details. .It Cm username = Qq Ar NAME Set username to drop privileges to after startup. Defaults to "nobody" which usually is defined on all UNIX systems. .It Cm virtual-host = Ar Enable virtual hosting, disabled by default. For more information on this, see .Xr merecat 8 . .It Cm user-agent-deny = Qq Ar PATTERN Wildcard pattern to deny access to illicit hammering bots. When set a matching user-agent will receive a 403 for all its requests. Use for instance .Qq **SemrushBot** or .Qq **SemrushBot**|**MJ12Bot**|**DotBot** to match multiple user-agents. .Pp The default is disabled, i.e. all user-agents are allowed. .It Cm cgi Qo Ar PATTERN Qc Cm { Wildcard pattern for CGI programs, for instance .Qq **.cgi or .Qq **.cgi|/cgi-bin/* . See the dedicated CGI section in .Xr merecat 8 for more on this. .Pp .Bl -tag -offset "" -compact .It Cm enabled = Ar The CGI module is disabled by default. .It Cm limit = Ar NUM Maximum number of allowed simultaneous CGI programs. Default 1. .El .It Cm } .It Cm php Qo Ar PATTERN Qc Cm { Wildcard pattern for PHP scripts, for instance .Qq **.php* or .Qq **.php5*|**.php4*|**.php* . Notice the trailing .Cm * , it is very important otherwise any HTTP GET request with arguments will fail. .Pp .Bl -tag -offset "" -compact .It Cm enabled = Ar The PHP module is disabled by default. .It Cm cgi-path = Qq Pa /path/to/php-cgi Default is .Qq Pa /usr/bin/php-cgi .El .It Cm } .It Cm ssi Qo Ar PATTERN Qc Cm { Wildcard pattern for triggering SSI, for instance .Qq **.shtml or .Qq **.shtml|**.stm|**.shtm . .Pp .Bl -tag -offset "" -compact .It Cm enabled = Ar The SSI module is disabled by default. .It Cm cgi-path = Qq Pa /path/to/ssi Default is .Qq Pa cgi-bin/ssi . See .Xr ssi 8 for more information. .It Cm silent = Ar This setting can be used to silence “[an error occurred while processing the directive]”, shown when an error occurrs during SSI processing. Default disabled (false). .El .It Cm } .It Cm ssl Cm { .Bl -tag -offset "" -compact .It Cm protocol = Qq Ar PROTOCOL Minimum SSL/TLS protocol level to enable. Can be one of: .Ar SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 . The default minimum protocol is .Ar TLSv1.1 . Note, some (Linux) distributions have .Ar SSLv3 disabled by default in their OpenSSL packages. .It Cm ciphers = Qq Ar CIPHERS The preferred list of ciphers the server supports. For a list of available ciphers, see the .Xr ciphers 1 man page. The default covers both TLSv1.3 (new ciphersuite) and older cipher list: .Bd -unfilled -offset indent TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256: \\ HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4:!DHE-RSA-CAMELLIA256-SHA: \\ !DHE-RSA-CAMELLIA128-SHA:!ECDHE-RSA-CHACHA20-POLY1305: \\ !DHE-RSA-CHACHA20-POLY1305:!DHE-RSA-AES256-CCM8:!DHE-RSA-AES256-CCM: \\ !DHE-RSA-AES128-CCM8:!DHE-RSA-AES128-CCM .Ed .Pp .It Cm certfile = Ar /path/to/cert.pem Public part of HTTPS certificate, optionally with full certificate chain. E.g., .Cm fullchain.pem if you use Let's Encrypt. Only PEM format is supported. .It Cm keyfile = Ar /path/to/key.pem Private key of HTTPS certificate, e.g., .Cm privkey.pem if you use Let's Encrypt. Only PEM format is supported. .Pp .Sy Note: This file must be kept private and should not be in the WEBROOT directory. .It Cm dhfile = Ar /path/th/dhparam.pem Optional Diffie-Hellman parameters. Not secret, unlike the .Cm keyfile the .Cm dhfile can be published online, if necessary. Create one like this: .Bd -unfilled -offset indent openssl dhparam -out dhparam.pem 2048 .Ed .El .It Cm } .It Cm server Ar name Cm { .Bl -tag -offset "" -compact .It Cm port = Ar PORT Server port to listen to. .It Cm ssl Cm { Ar ... Cm } Same as the global settings, above, only for this server. .It Cm location Qo Ar PATTERN Qc { .Bl -tag -offset "" -compact .It Cm path = Ar path/to/rewrite If a server location directive is found it has precedence over any .Cm redirect or virtual host. It is primarily used to rewrite, or redirect, requests inside the current server context. .Pp E.g., for handling .Nm certbot HTTP-01 renewal, use this in the port 80 server context. Any other path will be redirected to HTTPS, using the below .Cm redirect directive: .Pp .Bd -unfilled -offset "" -compact location "/.well-known/acme-challenge/**" { path = "letsencrypt/.well-known/acme-challenge/" } .Ed .El .It Cm redirect Qo Ar PATTERN Qc { .Bl -tag -offset "" -compact .It Cm code = Ar CODE HTTP redirect code to use, default: 301. Supported codes are: 301, 302, 303, 307. .It Cm location = Qq Ar proto://$host:port$request_uri$args Location to return for redirect, e.g. to redirect all request for HTTP to HTTPS for the same (virtual) host: .Pp .Bd -unfilled -offset "" -compact redirect "/**" { code = 301 location = "https://$host$request_uri$args" } .Ed .El .It Cm } .El .It Cm } .El .Sh SEE ALSO .Xr merecat 8 .Sh AUTHORS .An -split .An Jef Poskanzer Aq jef@mail.acme.com wrote the famous .Nm thttpd which .Nm is based on. .An Joachim Wiberg Aq troglobit@gmail.com added the .conf file parser and this man page. merecat-2.31+git20220513+ds/man/ssi.8000066400000000000000000000151311424066547300165350ustar00rootroot00000000000000.\" -*- nroff -*- .\" The Merecat web server stems from both sthttpd and thttpd, both of .\" which are free software under the 2-clause simplified BSD license. .\" .\" Copyright (c) 1995-2015 Jef Poskanzer .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" .\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE .\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR .\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF .\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN .\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF .\" THE POSSIBILITY OF SUCH DAMAGE. .Dd Aug 3, 2019 .Dt SSI 8 SMM .Os "merecat (2.32)" .Sh NAME .Nm ssi .Nd Server-Side-Includes CGI program .Sh SYNOPSIS .Nm .Sh DESCRIPTION This is an external CGI program that provides the same functionality as the built-in server-side-includes feature in many HTTP daemons. It is written for use with .Xr thttpd 8 and .Xr merecat 8 , but should be easy to adapt to other systems. .Pp There are two ways to use this; the modern way of using a .Cm .shtml pattern in .Xr merecat.conf 5 to trigger the SSI script, which requires enabling the SSI module. Then there is the traditional .Xr thttpd 8 approach. We start with the relevant settings needed in .Nm merecat.conf : .Bd -literal -offset indent ssi { enabled = true pattern = "**.shtml" # default cgi-path = "cgi-bin/ssi" # default, web server root is used } .Ed .Pp The traditional .Nm thttpd way runs ssi as a simple CGI script, which requires placing the ssi binary in the web server CGI area, and enabling CGI. Then set up URLs with the path to the document to parse as the "pathinfo". That's the part of the URL that comes after the CGI program name. For example, if the URL to this program is: .Bd -unfilled -offset left http://www.acme.com/cgi-bin/ssi .Ed and the url for the document is: .Bd -unfilled -offset left http://www.acme.com/users/wecoyote/doc.html .Ed then the compound URL would be: .Bd -unfilled -offset left http://www.acme.com/cgi-bin/ssi/users/wecoyote/doc.html .Ed .Sh INCLUDE FORMAT The format description below is adapted from .Aq http://hoohoo.ncsa.uiuc.edu/docs/tutorials/includes.html . .Pp All directives are formatted as SGML comments within the document. This is in case the document should ever find itself in the client's hands unparsed. Each directive has the following format: .Bd -unfilled -offset left .Ed .Em Note: the lack of space between the initial HTML comment start and the #command. This is explicitly stated in the standard and strictly enforced by all web servers implementing SSI. .Pp Each command takes different arguments, most only accept one tag at a time. Here is a breakdown of the commands and their associated tags: .Bl -tag -width Ds .It Cm config The config directive controls various aspects of the file parsing. There are two valid tags: .Bl -tag -width Ds .It Cm timefmt gives the server a new format to use when providing dates. This is a string compatible with the .Xr strftime 3 library call. .It Cm sizefmt determines the formatting to be used when displaying the size of a file. Valid choices are bytes, for a formatted byte count (formatted as 1,234,567), or abbrev for an abbreviated version displaying the number of kilobytes or megabytes the file occupies. .It Cm errmsg overrides the default; .Qq [an error occurred while processing this directive] .El .It Cm include Inserts the text of another document into the parsed document. The inserted file is parsed recursively, so it can contain server-side-include directives too. This command accepts two tags: .Bl -tag -width Ds .It Cm virtual Gives a virtual path to a document on the server. .It Cm file Gives a pathname relative to the current directory. ../ cannot be used in this pathname, nor can absolute paths be used. .El .It Cm echo Prints the value of one of the include variables (defined below). Any dates are printed subject to the currently configured timefmt. The only valid tag to this command is var, whose value is the name of the variable you wish to echo. .It Cm fsize prints the size of the specified file, subject to the sizefmt parameter to the config command. Valid tags are the same as with the include command. .It Cm flastmod prints the last modification date of the specified file, subject to the formatting preference given by the timefmt parameter to config. Valid tags are the same as with the include command. .El .Sh VARIABLES A number of variables are made available to parsed documents. In addition to the CGI variable set, the following variables are made available: .Bl -tag -width Ds .It Cm DOCUMENT_NAME The current filename. .It Cm DOCUMENT_URI The virtual path to this document (such as /~robm/foo.shtml). .It Cm QUERY_STRING_UNESCAPED The unescaped version of any search query the client sent. .It Cm DATE_LOCAL The current date, local time zone. Subject to the timefmt parameter to the config command. .It Cm DATE_GMT Same as .Cm DATE_LOCAL but in Greenwich mean time (GMT). .It Cm LAST_MODIFIED The last modification date of the current document. Subject to timefmt like the others. .El .Sh SEE ALSO .Xr merecat 8 , .Xr merecat.conf 5 , .Xr strftime 3 .Sh AUTHORS .An -split .An Jef Poskanzer Aq jef@mail.acme.com wrote the original for use with .Nm thttpd . .An Joachim Wiberg Aq troglobit@gmail.com added minor features and a trigger in .Nm merecat for .Cm .shtml pages. .Sh BUGS Does not implement all "modern" SSI directives are supported. E.g., .Cm exec cgi and .Cm exec cmd or any control directives like .Cm if, elif, else, endif, etc. Patches and pull-requests are welcome :) merecat-2.31+git20220513+ds/merecat.conf000066400000000000000000000106261424066547300173660ustar00rootroot00000000000000## /etc/merecat.conf -*-conf-unix-*- ## This is a sample configuration file for Merecat httpd ## For more help and more settings, see merecat.conf(5). ## ## what interface to bind to? ## (default is binding to any interface) #hostname=www.example.org ## Port to listen to, overrides command line argument ## Defaults to 80, or 443 when enabling HTTPS #port = 80 ## Unpriviliged user to run as, usually nobody or www-data #username = nobody ## Global .htpasswd (true) or local per-directory (false) #global-passwd = false ## Chrooting is a security measure which means isolating the webserver's ## access to files only available from a the given directory. To access ## files outside the chroot the administrator can either copy or bind ## mount files and directories into the chroot. #chroot = false ## Only useful if not chrooting #check-symlinks = false ## Alt. charset=iso-8859-1 #charset = UTF-8 ## Deflate (gzip) compression level: -1 .. 9 ## -1: Default (zlib's reasonable default, currently 6) ## 0: Disabled ## 1: Best speed ## 9: Best compression #compression-level = -1 ## Webserver document root, or chroot #directory = /var/www ## When chrooting, alt. document root inside chroot ## => /var/www/htdocs #data-directory = /htdocs ## Skip dotfiles in dirlistings #list-dotfiles = false ## Virtual hosting ## /var/www/cgi-bin/ <-- Shared CGI ## /var/www/git.example.com <-- git.example.com ## /var/www/ftp.example.com <-- ftp.example.com #virtual-host = false ## Control the caching, in seconds, by setting the following header for ## all transactions. Depends heavily on the content you provide, and ## this global setting is disabled by default. It is recommended to ## instead set it per server location, e.g. for all image files. ## ## Cache-Control: max-age=SEC ## ## Min max-age value 0 (browser caching disabled) ## Max max-age value 31536000 (1 year) ## #max-age = 0 ## Some bots behave really badly and may overload your server. Often ## they cannot be blocked based on IP address, so the only means we are ## left with is User-Agent blocking. Use patterns like this: #user-agent-deny = "**SemrushBot**|**MJ12bot**|**DotBot**|**PetalBot**" ## Enable HTTPS support. The certificate (public) and key (private) are ## required when enabling HTTPS support. The (min) protocol and cipher ## settings are optional and have sane built-in defaults, e.g. 'protocol' ## defaults to TLSv1.1. See ciphers(1) man page for possible values. ## ## Note: You may want to enable this on a per-server basis instead. #ssl { # protocol = "TLSv1.1" # ciphers = "..." # certfile = certs/cert.pem # keyfile = private/key.pem # dhfile = certs/dhparam.pem #} ## The CGI module is a core part of Merecat httpd and is for security ## reasons disabled by default. Like other modules it uses pattern ## matching to trigger the CGI functionality: ## ? match a single char ## * matches any string excluding "/" ## ** matches any string including "/" ## separate multiple patterns with "|" ## Example: "**.sh|**.cgi" ## ## `limit` sets the max number of simultaneous CGI programs allowed. ## ## The below values are the default, so to enable CGI only `enabled` ## need to be set to 'true'. #cgi "**.cgi|/cgi-bin/*" { # enabled = false # limit = 50 #} ## The PHP module is bolted on top of the CGI module, so the same limits ## apply also to PHP scripts. The below are the built-in defaults. ## Verify the path to the php-cgi binary for your system and expand on ## the pattern if you have, e.g. .php5 files. #php "**.php*" { # enabled = false # cgi-path = "/usr/bin/php-cgi" #} ## The SSI module, like PHP above, is built on top of the CGI module, ## and it also requires the Merecat SSI CGI script to be installed, the ## defaults are commented out below. The silent setting controls the ## default value. #ssi "**.shtml" { # enabled = false # silent = false # cgi-path = "cgi-bin/ssi" #} ## Server specific settings, overrides certain global settings ## Notice the HTTP redirect from the default server to HTTPS. #server default { # port = 80 # redirect "/" { # code = 301 # location = "https://$host$request_uri$args" # } #} #server secure { # port = 443 # ssl = on # certfile = /etc/letsencrypt/live/example.com/fullchain.pem # keyfile = /etc/letsencrypt/live/example.com/privkey.pem # dhfile = certs/dhparam.pem #} merecat-2.31+git20220513+ds/merecat.service.in000066400000000000000000000005141424066547300205010ustar00rootroot00000000000000[Unit] Description=Merecat web server Documentation=man:merecat(8) man:merecat.conf(5) man:ssi(8) man:htpasswd(1) ConditionPathExists=@WWWDIR@ After=network-online.target Requires=network-online.target [Service] Type=simple Restart=always RestartSec=3 ExecStart=@SBINDIR@/merecat -sn @WWWDIR@ [Install] WantedBy=multi-user.target merecat-2.31+git20220513+ds/run.sh000077500000000000000000000003661424066547300162420ustar00rootroot00000000000000#!/bin/sh # Serve volume /srv/ftp with merecat httpd on host port 80 docker pull troglobit/merecat:latest docker run -dit --restart unless-stopped -h `hostname` \ -v /srv/ftp:/var/www -p 80:80 troglobit/merecat:latest merecat -n /var/www merecat-2.31+git20220513+ds/src/000077500000000000000000000000001424066547300156615ustar00rootroot00000000000000merecat-2.31+git20220513+ds/src/.gitignore000066400000000000000000000000421424066547300176450ustar00rootroot00000000000000.deps htpasswd libmatch.a merecat merecat-2.31+git20220513+ds/src/Makefile.am000066400000000000000000000035701424066547300177220ustar00rootroot00000000000000AUTOMAKE_OPTIONS = subdir-objects sbin_PROGRAMS = merecat if ENABLE_HTPASSWD sbin_PROGRAMS += htpasswd endif # in.httpd SYMLINK = httpd merecat_CFLAGS = -fPIC -W -Wall -Wextra -std=gnu99 merecat_CFLAGS += -Wno-unused-result -Wno-unused-parameter -Wno-unused-variable merecat_CFLAGS += $(zlib_CFLAGS) merecat_CPPFLAGS = -D_POSIX_SOURCE -D_BSD_SOURCE -D_GNU_SOURCE -D_DEFAULT_SOURCE merecat_CPPFLAGS += -DCONFDIR='"$(sysconfdir)"' -DLOCALSTATEDIR='"$(localstatedir)"' merecat_CPPFLAGS += -DRUNDIR='"$(runstatedir)"' merecat_LDADD = libmatch.a $(zlib_LIBS) merecat_LDADD += $(LIBS) $(LIBOBJS) merecat_SOURCES = base64.c base64.h \ fdwatch.c fdwatch.h \ file.c file.h \ libhttpd.c libhttpd.h \ md5.c md5.h \ merecat.c merecat.h \ mmc.c mmc.h \ pidfile.c stack.c \ srv.c srv.h \ timers.c timers.h \ tdate_parse.c tdate_parse.h \ mime_encodings.h mime_types.h if ENABLE_SSL merecat_SOURCES += ssl.c ssl.h merecat_CFLAGS += $(OpenSSL_CFLAGS) merecat_LDADD += $(OpenSSL_LIBS) endif if HAVE_CONFUSE merecat_SOURCES += conf.c conf.h merecat_CFLAGS += $(confuse_CFLAGS) merecat_LDADD += $(confuse_LIBS) endif if ENABLE_HTPASSWD htpasswd_CFLAGS = -W -Wall -Wextra -Wno-unused-result -Wno-unused-parameter htpasswd_CPPFLAGS = -DWEBDIR='"$(WEBDIR)"' htpasswd_SOURCES = htpasswd.c endif noinst_LIBRARIES = libmatch.a libmatch_a_SOURCES = match.c match.h # Hook in install merecat --> in.httpd, httpd symlinks if CREATE_SYMLINKS install-exec-hook: @for file in $(SYMLINK); do \ link=$(DESTDIR)$(sbindir)/$$file; \ [ "`readlink $$link`" = "merecat" ] && continue; \ $(LN_S) merecat $$link; \ done uninstall-hook: @for file in $(SYMLINK); do \ $(RM) $(DESTDIR)$(sbindir)/$$file; \ done endif merecat-2.31+git20220513+ds/src/base64.c000066400000000000000000000051061424066547300171130ustar00rootroot00000000000000/* Base-64 decoding. This represents binary data as printable ASCII ** characters. Three 8-bit binary bytes are turned into four 6-bit ** values, like so: ** ** [11111111] [22222222] [33333333] ** ** [111111] [112222] [222233] [333333] ** ** Then the 6-bit values are represented using the characters "A-Za-z0-9+/". */ static int b64_decode_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2F */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 30-3F */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 40-4F */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5F */ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6F */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0-AF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0-BF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0-CF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0-DF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0-EF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* F0-FF */ }; /* Do base-64 decoding on a string. Ignore any non-base64 bytes. ** Return the actual number of bytes generated. The decoded size will ** be at most 3/4 the size of the encoded, and may be smaller if there ** are padding characters (blanks, newlines). */ int b64_decode(const char *str, unsigned char *space, int size) { const char *cp; int space_idx, phase; int prev_d = 0; unsigned char c; space_idx = 0; phase = 0; for (cp = str; *cp != '\0'; ++cp) { int d; d = b64_decode_table[(int)*cp]; if (d != -1) { switch (phase) { case 0: ++phase; break; case 1: c = ((prev_d << 2) | ((d & 0x30) >> 4)); if (space_idx < size) space[space_idx++] = c; ++phase; break; case 2: c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2)); if (space_idx < size) space[space_idx++] = c; ++phase; break; case 3: c = (((prev_d & 0x03) << 6) | d); if (space_idx < size) space[space_idx++] = c; phase = 0; break; } prev_d = d; } } return space_idx; } merecat-2.31+git20220513+ds/src/base64.h000066400000000000000000000002271424066547300171170ustar00rootroot00000000000000/* Base-64 decoding */ #ifndef BASE64_H_ #define BASE64_H_ int b64_decode(const char *str, unsigned char *space, int size); #endif /* BASE64_H_ */ merecat-2.31+git20220513+ds/src/conf.c000066400000000000000000000242031424066547300167530ustar00rootroot00000000000000/* libConfuse based merecat.conf parser ** ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #ifdef HAVE_ZLIB_H #include #endif #include "conf.h" #include "merecat.h" static cfg_t *cfg = NULL; static void conf_errfunc(cfg_t *cfg, const char *format, va_list args) { char fmt[80]; if (cfg && cfg->filename && cfg->line) snprintf(fmt, sizeof(fmt), "%s:%d: %s", cfg->filename, cfg->line, format); else if (cfg && cfg->filename) snprintf(fmt, sizeof(fmt), "%s: %s", cfg->filename, format); else snprintf(fmt, sizeof(fmt), "%s", format); vsyslog(LOG_ERR, fmt, args); } static void conf_cgi(cfg_t *cfg) { if (!cfg) return; cgi_pattern = (char *)cfg_title(cfg); cgi_enabled = cfg_getbool(cfg, "enabled"); cgi_limit = cfg_getint(cfg, "limit"); } static void conf_php(cfg_t *cfg) { if (!cfg || !cfg_getbool(cfg, "enabled")) { err: php_pattern = NULL; php_cgi = NULL; return; } php_cgi = cfg_getstr(cfg, "cgi-path"); php_pattern = (char *)cfg_title(cfg); if (!php_pattern || !php_cgi || access(php_cgi, X_OK)) { syslog(LOG_WARNING, "Invalid PHP settings, check your php-cgi path and pattern!"); goto err; } } static void conf_ssi(cfg_t *cfg) { if (!cfg || !cfg_getbool(cfg, "enabled")) { err: ssi_cgi = NULL; ssi_pattern = NULL; return; } ssi_cgi = cfg_getstr(cfg, "cgi-path"); ssi_silent = cfg_getbool(cfg, "silent"); ssi_pattern = (char *)cfg_title(cfg); if (!ssi_pattern || !ssi_cgi || access(ssi_cgi, X_OK)) { syslog(LOG_WARNING, "Invalid SSI settings, check path and pattern!"); goto err; } } static void conf_redirect(struct srv *srv, cfg_t *cfg) { size_t i; for (i = 0; i < cfg_size(cfg, "redirect") && i < NELEMS(srv->redirect); i++) { cfg_t *red; red = cfg_getnsec(cfg, "redirect", i); if (!red) return; srv->redirect[i].pattern = (char *)cfg_title(red); srv->redirect[i].code = cfg_getint(red, "code"); srv->redirect[i].location = cfg_getstr(red, "location"); } } static void conf_srv_location(struct srv *srv, cfg_t *cfg) { size_t i; for (i = 0; i < cfg_size(cfg, "location") && i < NELEMS(srv->location); i++) { cfg_t *loc; loc = cfg_getnsec(cfg, "location", i); if (!loc) return; srv->location[i].pattern = (char *)cfg_title(loc); srv->location[i].path = cfg_getstr(loc, "path"); } } static void conf_ssl(struct srv *srv, cfg_t *cfg) { cfg_t *ssl; ssl = cfg_getnsec(cfg, "ssl", 0); if (!ssl) { srv->ssl = 0; return; } #ifndef ENABLE_SSL syslog(LOG_ERR, "%s is not built with HTTPS support", PACKAGE_NAME); #else srv->ssl = 1; srv->ssl_proto = cfg_getstr(ssl, "protocol"); srv->ciphers = cfg_getstr(ssl, "ciphers"); srv->certfile = cfg_getstr(ssl, "certfile"); srv->keyfile = cfg_getstr(ssl, "keyfile"); srv->dhfile = cfg_getstr(ssl, "dhfile"); /* Optional */ if (!srv->certfile || !srv->keyfile) syslog(LOG_ERR, "Missing SSL certificate file(s)"); #endif } int conf_srv(struct srv arr[], size_t len) { size_t i; if (!cfg) { arr[0].title = "default"; arr[0].host = hostname; arr[0].port = port; arr[0].path = path; arr[0].ssl = 0; return 1; } if (cfg_size(cfg, "server") == 0) { arr[0].title = "default"; arr[0].host = cfg_getstr(cfg, "hostname"); arr[0].port = cfg_getint(cfg, "port"); arr[0].path = path; conf_ssl(&arr[0], cfg); return 1; } for (i = 0; i < cfg_size(cfg, "server") && i < len; i++) { cfg_t *srv; srv = cfg_getnsec(cfg, "server", i); if (!srv) return -1; arr[i].title = (char *)cfg_title(srv); arr[i].host = cfg_getstr(srv, "hostname"); arr[i].port = cfg_getint(srv, "port"); arr[i].path = cfg_getstr(srv, "path"); conf_ssl(&arr[i], srv); conf_redirect(&arr[i], srv); conf_srv_location(&arr[i], srv); } return (int)i; } static int read_config(char *fn) { cfg_opt_t location_opts[] = { CFG_STR ("path", NULL, CFGF_NONE), CFG_END () }; cfg_opt_t redirect_opts[] = { CFG_STR ("location", NULL, CFGF_NONE), CFG_INT ("code", 301, CFGF_NONE), CFG_END () }; cfg_opt_t cgi_opts[] = { CFG_BOOL("enabled", 0, CFGF_NONE), CFG_INT ("limit", cgi_limit, CFGF_NONE), CFG_END () }; cfg_opt_t php_opts[] = { CFG_BOOL("enabled", 0, CFGF_NONE), CFG_STR ("pattern", "**.php", CFGF_NONE), CFG_STR ("cgi-path", "/usr/bin/php-cgi", CFGF_NONE), CFG_END () }; cfg_opt_t ssi_opts[] = { CFG_BOOL("enabled", 0, CFGF_NONE), CFG_BOOL("silent", 0, CFGF_NONE), CFG_STR ("pattern", "**.shtml", CFGF_NONE), CFG_STR ("cgi-path", "cgi-bin/ssi", CFGF_NONE), CFG_END () }; cfg_opt_t ssl_opts[] = { CFG_STR ("protocol", SSL_DEFAULT_PROTO, CFGF_NONE), CFG_STR ("ciphers", SSL_DEFAULT_CIPHERS, CFGF_NONE), CFG_STR ("certfile", NULL, CFGF_NONE), CFG_STR ("keyfile", NULL, CFGF_NONE), CFG_STR ("dhfile", NULL, CFGF_NONE), CFG_END () }; cfg_opt_t server_opts[] = { CFG_STR ("hostname", hostname, CFGF_NONE), CFG_INT ("port", port, CFGF_NONE), CFG_STR ("path", path, CFGF_NONE), CFG_SEC ("location", location_opts, CFGF_MULTI | CFGF_TITLE), CFG_SEC ("ssl", ssl_opts, CFGF_MULTI), CFG_SEC ("redirect", redirect_opts, CFGF_MULTI | CFGF_TITLE), CFG_END () }; cfg_opt_t opts[] = { CFG_INT ("port", port, CFGF_NONE), CFG_BOOL("chroot", do_chroot, CFGF_NONE), CFG_INT ("compression-level", compression_level, CFGF_NONE), CFG_STR ("directory", dir, CFGF_NONE), CFG_STR ("data-directory", data_dir, CFGF_NONE), CFG_BOOL("global-passwd", do_global_passwd, CFGF_NONE), CFG_BOOL("check-symlinks", !no_symlink_check, CFGF_NONE), CFG_BOOL("check-referer", cfg_false, CFGF_NONE), CFG_STR ("charset", charset, CFGF_NONE), CFG_BOOL("list-dotfiles", cfg_false, CFGF_NONE), CFG_STR ("local-pattern", NULL, CFGF_NONE), CFG_STR ("url-pattern", NULL, CFGF_NONE), CFG_INT ("max-age", 0, CFGF_NONE), CFG_STR ("username", user, CFGF_NONE), CFG_STR ("hostname", hostname, CFGF_NONE), CFG_BOOL("virtual-host", do_vhost, CFGF_NONE), CFG_STR ("user-agent-deny", useragent_deny, CFGF_NONE), CFG_SEC ("cgi", cgi_opts, CFGF_MULTI | CFGF_TITLE), CFG_SEC ("php", php_opts, CFGF_MULTI | CFGF_TITLE), CFG_SEC ("ssi", ssi_opts, CFGF_MULTI | CFGF_TITLE), CFG_SEC ("ssl", ssl_opts, CFGF_MULTI), CFG_SEC ("server", server_opts, CFGF_MULTI | CFGF_TITLE), CFG_END () }; int rc = 0; cfg = cfg_init(opts, CFGF_NONE); if (!cfg) { syslog(LOG_ERR, "Failed initializing configuration file parser: %s", strerror(errno)); return 1; } /* Custom logging, rather than default Confuse stderr logging */ cfg_set_error_function(cfg, conf_errfunc); rc = cfg_parse(cfg, fn); switch (rc) { case CFG_FILE_ERROR: syslog(LOG_ERR, "Cannot read configuration file %s", fn); goto error; case CFG_PARSE_ERROR: syslog(LOG_ERR, "Parse error in %s", fn); cfg_free(cfg); exit(1); case CFG_SUCCESS: break; } port = cfg_getint(cfg, "port"); do_chroot = cfg_getbool(cfg, "chroot"); if (do_chroot) no_symlink_check = 1; dir = cfg_getstr(cfg, "directory"); data_dir = cfg_getstr(cfg, "data-directory"); if (cfg_getbool(cfg, "check-symlinks")) no_symlink_check = 0; user = cfg_getstr(cfg, "username"); url_pattern = cfg_getstr(cfg, "url-pattern"); local_pattern = cfg_getstr(cfg, "local-pattern"); useragent_deny = cfg_getstr(cfg, "user-agent-deny"); no_empty_referers = cfg_getbool(cfg, "check-referer"); do_list_dotfiles = cfg_getbool(cfg, "list-dotfiles"); hostname = cfg_getstr(cfg, "hostname"); do_vhost = cfg_getbool(cfg, "virtual-host"); do_global_passwd = cfg_getbool(cfg, "global-passwd"); charset = cfg_getstr(cfg, "charset"); max_age = cfg_getint(cfg, "max-age"); #ifdef HAVE_ZLIB_H compression_level = cfg_getint(cfg, "compression-level"); if (compression_level < Z_DEFAULT_COMPRESSION) compression_level = Z_DEFAULT_COMPRESSION; if (compression_level > Z_BEST_COMPRESSION) compression_level = Z_BEST_COMPRESSION; #endif conf_cgi(cfg_getnsec(cfg, "cgi", 0)); conf_php(cfg_getnsec(cfg, "php", 0)); conf_ssi(cfg_getnsec(cfg, "ssi", 0)); return 0; error: cfg_free(cfg); cfg = NULL; return 1; } int conf_init(char *file) { char path[MAXPATHLEN + 1]; if (!file) { snprintf(path, sizeof(path), "%s/%s.conf", CONFDIR, ident); file = path; /* * If default .conf doesn't exist, fail silent. * We must support running stand-alone as well. */ if (access(file, F_OK)) return 0; } else { /* * Support stand-alone also if `-f none` */ if (!strcmp(file, "none")) return 0; /* * If `-f foo.conf` doesn't exist, we must bail, the * user expects their settings from the .conf not any * built-in defaults. */ if (access(file, F_OK)) { syslog(LOG_ERR, "%s: %s: %s", prognm, file, strerror(errno)); return 1; } } if (read_config(file)) { fprintf(stderr, "%s: Failed reading config file '%s'\n", prognm, file); return 1; } return 0; } void conf_exit(void) { cfg_free(cfg); } merecat-2.31+git20220513+ds/src/conf.h000066400000000000000000000056441424066547300167700ustar00rootroot00000000000000/* libConfuse based merecat.conf parser ** ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MERECAT_CONF_H_ #define MERECAT_CONF_H_ #include #include #include #include "merecat.h" #include "srv.h" /* From The Practice of Programming, by Kernighan and Pike */ #ifndef NELEMS #define NELEMS(array) (sizeof(array) / sizeof(array[0])) #endif /* Command line argument always wins */ struct conf { struct conf *prev, *next; char *hostname; char *user; /* DEFAULT_USER or command line */ uint16_t port; /* ... or command line */ bool vhost; bool check_referer; bool dotfiles; bool global_pwd; /* ... or command line */ bool chroot; /* ... or command line */ char *dir; /* SERVER_DIR_DEFUALT: /var/www or command line */ char *data_dir; char *cgi_pattern; /* CGI_PATTERN or command line */ int cgi_limit; /* CGI_LIMIT */ char *url_pattern; char *local_pattern; char *useragent_deny; char *charset; /* DEFAULT_CHARSET */ int max_age; /* DEFAULT_MAX_AGE */ int z_level; /* DEFAULT_COMPRESSION: For content-encoding: gzip */ bool ssl; char *certfile; char *keyfile; char *dhfile; }; #ifdef HAVE_LIBCONFUSE int conf_init(char *file); void conf_exit(void); int conf_srv(struct srv *arr, size_t len); #else #define conf_init(foo) #define conf_exit() static inline int conf_srv(struct srv *arr, size_t len) { arr[0].port = port; arr[0].ssl = 0; arr[0].host = hostname; arr[0].path = data_dir; return 1; } #endif #endif /* MERECAT_CONF_H_ */ merecat-2.31+git20220513+ds/src/fdwatch.c000066400000000000000000000456731424066547300174640ustar00rootroot00000000000000/* fdwatch.c - fd watcher routines, either select() or poll() ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifdef HAVE_POLL_H #include #else /* HAVE_POLL_H */ #ifdef HAVE_SYS_POLL_H #include #endif /* HAVE_SYS_POLL_H */ #endif /* HAVE_POLL_H */ #ifdef HAVE_SYS_DEVPOLL_H #include #ifndef HAVE_DEVPOLL #define HAVE_DEVPOLL #endif /* !HAVE_DEVPOLL */ #endif /* HAVE_SYS_DEVPOLL_H */ #ifdef HAVE_SYS_EVENT_H #include #endif /* HAVE_SYS_EVENT_H */ #include "fdwatch.h" #ifdef HAVE_SELECT #ifndef FD_SET #define NFDBITS 32 #define FD_SETSIZE 32 #define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS))) #define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS))) #define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS))) #define FD_ZERO(p) memset((char *)(p), 0, sizeof(*(p))) #endif /* !FD_SET */ #endif /* HAVE_SELECT */ static int nfiles; static long nwatches; static int *fd_rw; static void **fd_data; static int nreturned, next_ridx; #ifdef HAVE_KQUEUE #define WHICH "kevent" #define INIT(nfiles) kqueue_init(nfiles) #define EXIT() kqueue_exit() #define ADD_FD(fd, rw) kqueue_add_fd(fd, rw) #define DEL_FD(fd) kqueue_del_fd(fd) #define WATCH(timeout_msecs) kqueue_watch(timeout_msecs) #define CHECK_FD(fd) kqueue_check_fd(fd) #define GET_FD(ridx) kqueue_get_fd(ridx) static int kqueue_init(int nfiles); static void kqueue_exit(void); static void kqueue_add_fd(int fd, int rw); static void kqueue_del_fd(int fd); static int kqueue_watch(long timeout_msecs); static int kqueue_check_fd(int fd); static int kqueue_get_fd(int ridx); #else /* HAVE_KQUEUE */ # ifdef HAVE_DEVPOLL #define WHICH "devpoll" #define INIT(nfiles) devpoll_init(nfiles) #define EXIT() devpoll_exit() #define ADD_FD(fd, rw) devpoll_add_fd(fd, rw) #define DEL_FD(fd) devpoll_del_fd(fd) #define WATCH(timeout_msecs) devpoll_watch(timeout_msecs) #define CHECK_FD(fd) devpoll_check_fd(fd) #define GET_FD(ridx) devpoll_get_fd(ridx) static int devpoll_init(int nfiles); static void devpoll_exit(void); static void devpoll_add_fd(int fd, int rw); static void devpoll_del_fd(int fd); static int devpoll_watch(long timeout_msecs); static int devpoll_check_fd(int fd); static int devpoll_get_fd(int ridx); # else /* HAVE_DEVPOLL */ # ifdef HAVE_POLL #define WHICH "poll" #define INIT(nfiles) poll_init(nfiles) #define EXIT() poll_exit() #define ADD_FD(fd, rw) poll_add_fd(fd, rw) #define DEL_FD(fd) poll_del_fd(fd) #define WATCH(timeout_msecs) poll_watch(timeout_msecs) #define CHECK_FD(fd) poll_check_fd(fd) #define GET_FD(ridx) poll_get_fd(ridx) static int poll_init(int nfiles); static void poll_exit(void); static void poll_add_fd(int fd, int rw); static void poll_del_fd(int fd); static int poll_watch(long timeout_msecs); static int poll_check_fd(int fd); static int poll_get_fd(int ridx); # else /* HAVE_POLL */ # ifdef HAVE_SELECT #define WHICH "select" #define INIT(nfiles) select_init(nfiles) #define EXIT() select_exit() #define ADD_FD(fd, rw) select_add_fd(fd, rw) #define DEL_FD(fd) select_del_fd(fd) #define WATCH(timeout_msecs) select_watch(timeout_msecs) #define CHECK_FD(fd) select_check_fd(fd) #define GET_FD(ridx) select_get_fd(ridx) static int select_init(int nfiles); static void select_exit(void); static void select_add_fd(int fd, int rw); static void select_del_fd(int fd); static int select_watch(long timeout_msecs); static int select_check_fd(int fd); static int select_get_fd(int ridx); # endif /* HAVE_SELECT */ # endif /* HAVE_POLL */ # endif /* HAVE_DEVPOLL */ #endif /* HAVE_KQUEUE */ /* Routines. */ /* Figure out how many file descriptors the system allows, and ** initialize the fdwatch data structures. Returns -1 on failure. */ int fdwatch_get_nfiles(void) { int i; #ifdef RLIMIT_NOFILE struct rlimit rl; #endif /* Figure out how many fd's we can have. */ nfiles = getdtablesize(); #ifdef RLIMIT_NOFILE /* If we have getrlimit(), use that, and attempt to raise the limit. */ if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { nfiles = rl.rlim_cur; if (rl.rlim_max == RLIM_INFINITY) rl.rlim_cur = 8192; /* arbitrary */ else if (rl.rlim_max > rl.rlim_cur) rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rl) == 0) nfiles = rl.rlim_cur; } #endif #if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) ) /* If we use select(), then we must limit ourselves to FD_SETSIZE. */ nfiles = MIN(nfiles, FD_SETSIZE); #endif /* Initialize the fdwatch data structures. */ nwatches = 0; fd_rw = (int *)malloc(sizeof(int) * nfiles); if (!fd_rw) return -1; fd_data = (void **)malloc(sizeof(void *) * nfiles); if (!fd_data) { free(fd_rw); return -1; } for (i = 0; i < nfiles; ++i) fd_rw[i] = -1; if (INIT(nfiles) == -1) { free(fd_rw); free(fd_data); return -1; } return nfiles; } void fdwatch_put_nfiles(void) { free(fd_rw); free(fd_data); EXIT(); } /* Add a descriptor to the watch list. rw is either FDW_READ or FDW_WRITE. */ void fdwatch_add_fd(int fd, void *arg, int rw) { if (fd < 0 || fd >= nfiles || fd_rw[fd] != -1) { syslog(LOG_ERR, "bad fd (%d) passed to fdwatch_add_fd(nfiles:%d)!", fd, nfiles); return; } ADD_FD(fd, rw); fd_rw[fd] = rw; fd_data[fd] = arg; } /* Remove a descriptor from the watch list. */ void fdwatch_del_fd(int fd) { if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1) { syslog(LOG_ERR, "bad fd (%d) passed to fdwatch_del_fd!", fd); return; } DEL_FD(fd); fd_rw[fd] = -1; fd_data[fd] = NULL; } /* Do the watch. Return value is the number of descriptors that are ready, ** or 0 if the timeout expired, or -1 on errors. A timeout of INFTIM means ** wait indefinitely. */ int fdwatch(long timeout_msecs) { ++nwatches; nreturned = WATCH(timeout_msecs); next_ridx = 0; return nreturned; } /* Check if a descriptor was ready. */ int fdwatch_check_fd(int fd) { if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1) { syslog(LOG_ERR, "bad fd (%d) passed to fdwatch_check_fd!", fd); return 0; } return CHECK_FD(fd); } void *fdwatch_get_next_arg(void) { int fd; if (next_ridx >= nreturned) return (void *)-1; fd = GET_FD(next_ridx++); if (fd < 0 || fd >= nfiles) return NULL; return fd_data[fd]; } /* Generate debugging statistics syslog message. */ void fdwatch_logstats(long secs) { if (secs > 0) syslog(LOG_INFO, " fdwatch - %ld %ss (%g/sec)", nwatches, WHICH, (float)nwatches / secs); nwatches = 0; } #ifdef HAVE_KQUEUE static int maxkqevents; static struct kevent *kqevents; static int nkqevents; static struct kevent *kqrevents; static int *kqrfdidx; static int kq; static int kqueue_init(int nfiles) { kq = kqueue(); if (kq == -1) return -1; maxkqevents = nfiles * 2; kqevents = (struct kevent *)calloc(maxkqevents, sizeof(struct kevent)); if (!kqevents) return -1; kqrevents = (struct kevent *)calloc(nfiles, sizeof(struct kevent)); if (!kqrevents) { free(kqevents); return -1; } kqrfdidx = (int *)calloc(nfiles, sizeof(int)); if (!kqrfdidx) { free(kqevents); free(kqrevents); return -1; } return 0; } static void kqueue_exit(void) { free(kqevents); free(kqrevents); free(kqrfdidx); } static void kqueue_add_fd(int fd, int rw) { if (nkqevents >= maxkqevents) { syslog(LOG_ERR, "too many kqevents in kqueue_add_fd!"); return; } kqevents[nkqevents].ident = fd; kqevents[nkqevents].flags = EV_ADD; switch (rw) { case FDW_READ: kqevents[nkqevents].filter = EVFILT_READ; break; case FDW_WRITE: kqevents[nkqevents].filter = EVFILT_WRITE; break; default: break; } ++nkqevents; } static void kqueue_del_fd(int fd) { if (nkqevents >= maxkqevents) { syslog(LOG_ERR, "too many kqevents in kqueue_del_fd!"); return; } kqevents[nkqevents].ident = fd; kqevents[nkqevents].flags = EV_DELETE; switch (fd_rw[fd]) { case FDW_READ: kqevents[nkqevents].filter = EVFILT_READ; break; case FDW_WRITE: kqevents[nkqevents].filter = EVFILT_WRITE; break; } ++nkqevents; } static int kqueue_watch(long timeout_msecs) { int i, r; if (timeout_msecs == INFTIM) { r = kevent(kq, kqevents, nkqevents, kqrevents, nfiles, NULL); } else { struct timespec ts; ts.tv_sec = timeout_msecs / 1000L; ts.tv_nsec = (timeout_msecs % 1000L) * 1000000L; r = kevent(kq, kqevents, nkqevents, kqrevents, nfiles, &ts); } nkqevents = 0; if (r == -1) return -1; for (i = 0; i < r; ++i) kqrfdidx[kqrevents[i].ident] = i; return r; } static int kqueue_check_fd(int fd) { int ridx = kqrfdidx[fd]; if (ridx < 0 || ridx >= nfiles) { syslog(LOG_ERR, "bad ridx (%d) in kqueue_check_fd!", ridx); return 0; } if (ridx >= nreturned) return 0; if (kqrevents[ridx].ident != fd) return 0; if (kqrevents[ridx].flags & EV_ERROR) return 0; switch (fd_rw[fd]) { case FDW_READ: return kqrevents[ridx].filter == EVFILT_READ; case FDW_WRITE: return kqrevents[ridx].filter == EVFILT_WRITE; } return 0; } static int kqueue_get_fd(int ridx) { if (ridx < 0 || ridx >= nfiles) { syslog(LOG_ERR, "bad ridx (%d) in kqueue_get_fd!", ridx); return -1; } return kqrevents[ridx].ident; } #else /* HAVE_KQUEUE */ # ifdef HAVE_DEVPOLL static int maxdpevents; static struct pollfd *dpevents; static int ndpevents; static struct pollfd *dprevents; static int *dp_rfdidx; static int dp; static int devpoll_init(int nfiles) { dp = open("/dev/poll", O_RDWR); if (dp == -1) return -1; fcntl(dp, F_SETFD, 1); maxdpevents = nfiles * 2; dpevents = (struct pollfd *)calloc(sizeof(maxdpevents, struct pollfd)); if (!dpevents) { close(dp); return -1; } dprevents = (struct pollfd *)calloc(nfiles, sizeof(struct pollfd)); if (!dprevents) { close(dp); free(dpevents); return -1; } dp_rfdidx = (int *)calloc(nfiles, sizeof(int)); if (!dp_rfdidx) { close(dp); free(dpevents); free(dprevents); return -1; } return 0; } static void devpoll_exit(void) { free(dpevents); free(dprevents); free(dp_rfdidx); } static void devpoll_add_fd(int fd, int rw) { if (ndpevents >= maxdpevents) { syslog(LOG_ERR, "too many fds in devpoll_add_fd!"); return; } dpevents[ndpevents].fd = fd; switch (rw) { case FDW_READ: dpevents[ndpevents].events = POLLIN; break; case FDW_WRITE: dpevents[ndpevents].events = POLLOUT; break; } ++ndpevents; } static void devpoll_del_fd(int fd) { if (ndpevents >= maxdpevents) { syslog(LOG_ERR, "too many fds in devpoll_del_fd!"); return; } dpevents[ndpevents].fd = fd; dpevents[ndpevents].events = POLLREMOVE; ++ndpevents; } static int devpoll_watch(long timeout_msecs) { int i, r; struct dvpoll dvp; r = sizeof(struct pollfd) * ndpevents; if (r > 0 && write(dp, dpevents, r) != r) return -1; ndpevents = 0; dvp.dp_fds = dprevents; dvp.dp_nfds = nfiles; dvp.dp_timeout = (int)timeout_msecs; r = ioctl(dp, DP_POLL, &dvp); if (r == -1) return -1; for (i = 0; i < r; ++i) dp_rfdidx[dprevents[i].fd] = i; return r; } static int devpoll_check_fd(int fd) { int ridx = dp_rfdidx[fd]; if (ridx < 0 || ridx >= nfiles) { syslog(LOG_ERR, "bad ridx (%d) in devpoll_check_fd!", ridx); return 0; } if (ridx >= nreturned) return 0; if (dprevents[ridx].fd != fd) return 0; if (dprevents[ridx].revents & POLLERR) return 0; switch (fd_rw[fd]) { case FDW_READ: return dprevents[ridx].revents & (POLLIN | POLLHUP | POLLNVAL); case FDW_WRITE: return dprevents[ridx].revents & (POLLOUT | POLLHUP | POLLNVAL); } return 0; } static int devpoll_get_fd(int ridx) { if (ridx < 0 || ridx >= nfiles) { syslog(LOG_ERR, "bad ridx (%d) in devpoll_get_fd!", ridx); return -1; } return dprevents[ridx].fd; } # else /* HAVE_DEVPOLL */ # ifdef HAVE_POLL static struct pollfd *pollfds; static int npoll_fds; static int *poll_fdidx; static int *poll_rfdidx; static int poll_init(int nfiles) { int i; pollfds = (struct pollfd *)malloc(sizeof(struct pollfd) * nfiles); if (!pollfds) return -1; poll_fdidx = (int *)malloc(sizeof(int) * nfiles); if (!poll_fdidx) { free(pollfds); return -1; } poll_rfdidx = (int *)malloc(sizeof(int) * nfiles); if (!poll_rfdidx) { free(pollfds); free(poll_fdidx); return -1; } for (i = 0; i < nfiles; ++i) pollfds[i].fd = poll_fdidx[i] = poll_rfdidx[i] = -1; return 0; } static void poll_exit(void) { free(pollfds); free(poll_fdidx); free(poll_rfdidx); } static void poll_add_fd(int fd, int rw) { if (npoll_fds >= nfiles) { syslog(LOG_ERR, "too many fds in poll_add_fd!"); return; } pollfds[npoll_fds].fd = fd; switch (rw) { case FDW_READ: pollfds[npoll_fds].events = POLLIN; break; case FDW_WRITE: pollfds[npoll_fds].events = POLLOUT; break; default: break; } poll_fdidx[fd] = npoll_fds; ++npoll_fds; } static void poll_del_fd(int fd) { int idx = poll_fdidx[fd]; if (idx < 0 || idx >= nfiles) { syslog(LOG_ERR, "bad idx (%d) in poll_del_fd!", idx); return; } --npoll_fds; pollfds[idx] = pollfds[npoll_fds]; poll_fdidx[pollfds[idx].fd] = idx; pollfds[npoll_fds].fd = -1; poll_fdidx[fd] = -1; } static int poll_watch(long timeout_msecs) { int r, ridx, i; r = poll(pollfds, npoll_fds, (int)timeout_msecs); if (r <= 0) return r; ridx = 0; for (i = 0; i < npoll_fds; ++i) { if (pollfds[i].revents & (POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL)) { poll_rfdidx[ridx++] = pollfds[i].fd; if (ridx == r) break; } } return ridx; /* should be equal to r */ } static int poll_check_fd(int fd) { int fdidx = poll_fdidx[fd]; if (fdidx < 0 || fdidx >= nfiles) { syslog(LOG_ERR, "bad fdidx (%d) in poll_check_fd!", fdidx); return 0; } if (pollfds[fdidx].revents & POLLERR) return 0; switch (fd_rw[fd]) { case FDW_READ: return pollfds[fdidx].revents & (POLLIN | POLLHUP | POLLNVAL); case FDW_WRITE: return pollfds[fdidx].revents & (POLLOUT | POLLHUP | POLLNVAL); } return 0; } static int poll_get_fd(int ridx) { if (ridx < 0 || ridx >= nfiles) { syslog(LOG_ERR, "bad ridx (%d) in poll_get_fd!", ridx); return -1; } return poll_rfdidx[ridx]; } # else /* HAVE_POLL */ # ifdef HAVE_SELECT static fd_set master_rfdset; static fd_set master_wfdset; static fd_set working_rfdset; static fd_set working_wfdset; static int *select_fds; static int *select_fdidx; static int *select_rfdidx; static int nselect_fds; static int maxfd; static int maxfd_changed; static int select_init(int nfiles) { int i; FD_ZERO(&master_rfdset); FD_ZERO(&master_wfdset); select_fds = (int *)malloc(sizeof(int) * nfiles); if (!select_fds) return -1; select_fdidx = (int *)malloc(sizeof(int) * nfiles); if (!select_fdidx) { free(select_fds); return -1; } select_rfdidx = (int *)malloc(sizeof(int) * nfiles); if (!select_rfdidx) { free(select_fds); free(select_fdidx); return -1; } nselect_fds = 0; maxfd = -1; maxfd_changed = 0; for (i = 0; i < nfiles; ++i) select_fds[i] = select_fdidx[i] = -1; return 0; } static void select_exit(void) { free(select_fds); free(select_fdidx); free(select_rfdidx); } static void select_add_fd(int fd, int rw) { if (nselect_fds >= nfiles) { syslog(LOG_ERR, "too many fds in select_add_fd!"); return; } select_fds[nselect_fds] = fd; switch (rw) { case FDW_READ: FD_SET(fd, &master_rfdset); break; case FDW_WRITE: FD_SET(fd, &master_wfdset); break; default: break; } if (fd > maxfd) maxfd = fd; select_fdidx[fd] = nselect_fds; ++nselect_fds; } static void select_del_fd(int fd) { int idx = select_fdidx[fd]; if (idx < 0 || idx >= nfiles) { syslog(LOG_ERR, "bad idx (%d) in select_del_fd!", idx); return; } --nselect_fds; select_fds[idx] = select_fds[nselect_fds]; select_fdidx[select_fds[idx]] = idx; select_fds[nselect_fds] = -1; select_fdidx[fd] = -1; FD_CLR(fd, &master_rfdset); FD_CLR(fd, &master_wfdset); if (fd >= maxfd) maxfd_changed = 1; } static int select_get_maxfd(void) { if (maxfd_changed) { int i; maxfd = -1; for (i = 0; i < nselect_fds; ++i) { if (select_fds[i] > maxfd) maxfd = select_fds[i]; } maxfd_changed = 0; } return maxfd; } static int select_watch(long timeout_msecs) { int mfd; int r, idx, ridx; working_rfdset = master_rfdset; working_wfdset = master_wfdset; mfd = select_get_maxfd(); if (timeout_msecs == INFTIM) { r = select(mfd + 1, &working_rfdset, &working_wfdset, NULL, NULL); } else { struct timeval timeout; timeout.tv_sec = timeout_msecs / 1000L; timeout.tv_usec = (timeout_msecs % 1000L) * 1000L; r = select(mfd + 1, &working_rfdset, &working_wfdset, NULL, &timeout); } if (r <= 0) return r; ridx = 0; for (idx = 0; idx < nselect_fds; ++idx) { if (select_check_fd(select_fds[idx])) { select_rfdidx[ridx++] = select_fds[idx]; if (ridx == r) break; } } return ridx; /* should be equal to r */ } static int select_check_fd(int fd) { switch (fd_rw[fd]) { case FDW_READ: return FD_ISSET(fd, &working_rfdset); case FDW_WRITE: return FD_ISSET(fd, &working_wfdset); } return 0; } static int select_get_fd(int ridx) { if (ridx < 0 || ridx >= nfiles) { syslog(LOG_ERR, "bad ridx (%d) in select_get_fd!", ridx); return -1; } return select_rfdidx[ridx]; } # endif /* HAVE_SELECT */ # endif /* HAVE_POLL */ # endif /* HAVE_DEVPOLL */ #endif /* HAVE_KQUEUE */ merecat-2.31+git20220513+ds/src/fdwatch.h000066400000000000000000000070621424066547300174570ustar00rootroot00000000000000/* fdwatch.h - header file for fdwatch package ** ** This package abstracts the use of the select()/poll()/kqueue() ** system calls. The basic function of these calls is to watch a set ** of file descriptors for activity. select() originated in the BSD world, ** while poll() came from SysV land, and their interfaces are somewhat ** different. fdwatch lets you write your code to a single interface, ** with the portability differences hidden inside the package. ** ** Usage is fairly simple. Call fdwatch_get_nfiles() to initialize ** the package and find out how many fine descriptors are available. ** Then each time through your main loop, call fdwatch_clear(), then ** fdwatch_add_fd() for each of the descriptors you want to watch, ** then call fdwatch() to actually perform the watch. After it returns ** you can check which descriptors are ready via fdwatch_check_fd(). ** ** If your descriptor set hasn't changed from the last time through ** the loop, you can skip calling fdwatch_clear() and fdwatch_add_fd() ** to save a little CPU time. ** ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef FDWATCH_H_ #define FDWATCH_H_ #define FDW_READ 0 #define FDW_WRITE 1 #ifndef INFTIM #define INFTIM -1 #endif /* INFTIM */ /* Figure out how many file descriptors the system allows, and ** initialize the fdwatch data structures. Returns -1 on failure. */ extern int fdwatch_get_nfiles(void); /* Free initialized fdwatch data structues at exit */ extern void fdwatch_put_nfiles(void); /* Add a descriptor to the watch list. rw is either FDW_READ or FDW_WRITE. */ extern void fdwatch_add_fd(int fd, void *arg, int rw); /* Delete a descriptor from the watch list. */ extern void fdwatch_del_fd(int fd); /* Do the watch. Return value is the number of descriptors that are ready, ** or 0 if the timeout expired, or -1 on errors. A timeout of INFTIM means ** wait indefinitely. */ extern int fdwatch(long timeout_msecs); /* Check if a descriptor was ready. */ extern int fdwatch_check_fd(int fd); /* Get the client data for the next returned event. Returns -1 when there ** are no more events. */ extern void *fdwatch_get_next_arg(void); /* Generate debugging statistics syslog message. */ extern void fdwatch_logstats(long secs); #endif /* FDWATCH_H_ */ merecat-2.31+git20220513+ds/src/file.c000066400000000000000000000047051424066547300167520ustar00rootroot00000000000000/* Utility functions for file descriptor access ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include /* Read the requested buffer completely, accounting for interruptions. */ ssize_t file_read(int fd, void *buf, size_t len) { ssize_t sz, retry = 3; sz = 0; while ((size_t)sz < len) { ssize_t r; r = read(fd, (char *)buf + sz, len - sz); if (r < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { if (retry-- > 0) { sleep(1); continue; } } return r; } if (r == 0) break; sz += r; } return sz; } /* Write the requested buffer completely, accounting for interruptions. */ ssize_t file_write(int fd, void *buf, size_t len) { ssize_t sz, retry = 3; sz = 0; while ((size_t)sz < len) { ssize_t r; r = write(fd, (char *)buf + sz, len - sz); if (r < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { if (retry-- > 0) { sleep(1); continue; } continue; } return r; } if (r == 0) break; sz += r; } return sz; } merecat-2.31+git20220513+ds/src/file.h000066400000000000000000000032371424066547300167560ustar00rootroot00000000000000/* Utility functions for file descriptor access ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MERECAT_FILE_H_ #define MERECAT_FILE_H_ ssize_t file_read (int fd, void *buf, size_t len); ssize_t file_write (int fd, void *buf, size_t len); #endif /* MERECAT_FILE_H_ */ merecat-2.31+git20220513+ds/src/htpasswd.c000066400000000000000000000161201424066547300176620ustar00rootroot00000000000000/* * htpasswd.c: simple program for manipulating password file for NCSA httpd * * Rob McCool */ /* Modified 29aug97 by Jef Poskanzer to accept new password on stdin, ** if stdin is a pipe or file. This is necessary for use from CGI. */ #include #include #include #include #include #include #include #include #include #include #include extern char *crypt(const char *key, const char *setting); #define LF 10 #define CR 13 #define MAX_STRING_LEN 256 int tfd; char tmp[] = "/tmp/htp.XXXXXX"; struct termios saved; static void getword(char *word, char *line, char stop) { int x = 0, y; for (x = 0; ((line[x]) && (line[x] != stop)); x++) word[x] = line[x]; word[x] = '\0'; if (line[x]) ++x; y = 0; while ((line[y++] = line[x++])) ; } static int get_line(char *s, int n, FILE *f) { int i = 0; while (1) { s[i] = (char)fgetc(f); if (s[i] == CR) s[i] = fgetc(f); if ((s[i] == 0x4) || (s[i] == LF) || (i == (n - 1))) { s[i] = '\0'; return (feof(f) ? 1 : 0); } ++i; } } static void putline(FILE *f, char *l) { int x; for (x = 0; l[x]; x++) fputc(l[x], f); fputc('\n', f); } /* From local_passwd.c (C) Regents of Univ. of California blah blah */ static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static void to64(char *s, long v, size_t len) { size_t i; for (i = 0; i <= len; i++) { *s++ = itoa64[v & 0x3f]; v >>= 6; } } static char *get_password(const char *prompt, char *password, size_t len) { int c; char *pwd = NULL; size_t pos; struct termios term = saved; /* Disable XON/XOFF and ECHO while reading password string */ term.c_iflag &= ~(IXON|IXOFF); term.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &term); fputs(prompt, stderr); pos = 0; do { c = fgetc(stdin); if (isascii(c) && '\r' != c && '\n' != c) password[pos++] = c; } while (c != '\n' && pos < len); fputs("\n", stderr); password[pos] = 0; pwd = password; /* Restore TTY */ tcsetattr(STDIN_FILENO, TCSANOW, &saved); return pwd; } static void add_password(char *user, FILE *fp) { char pass[100]; char *pw; char *cpw; char salt[12] = "$1$"; /* Long enough for MD5 Crypt */ size_t index = 3; size_t saltlen = 8; const char *md5 = "$1$JASka/..$pV3V31AdjgqQmjTbgTNVu/"; /* Test if the system supports MD5 passwords */ if (strcmp(crypt("123456", md5), md5)) { /* The system does not support MD5 crypt: reset to default crypt. */ saltlen = 2; index = 0; } if (!isatty(STDIN_FILENO)) { if (!fgets(pass, sizeof(pass), stdin)) { fprintf(stderr, "Failed reading password from stdin: %s\n", strerror(errno)); exit(1); } if (pass[strlen(pass) - 1] == '\n') pass[strlen(pass) - 1] = '\0'; pw = pass; } else { pw = get_password("New password: ", pass, sizeof(pass)); if (!pw) { fail: fprintf(stderr, "Failed reading password.\n"); error: if (tfd != -1) unlink(tmp); exit(1); } pw = strdup(pw); cpw = get_password("Re-type password: ", pass, sizeof(pass)); if (!pw || !cpw) goto fail; if (strcmp(pw, cpw) != 0) { fprintf(stderr, "Passwords do not match, aborting.\n"); goto error; } } srandom(time(NULL)); to64(&salt[index], random(), saltlen); cpw = crypt(pw, salt); if (cpw) fprintf(fp, "%s:%s\n", user, cpw); else fprintf(stderr, "crypt() returned NULL, sorry\n"); } static int activate_template(char *template, char *file) { char *buf; FILE *in, *out; in = fopen(template, "r"); if (!in) return 1; out = fopen(file, "w"); if (!out) { fclose(in); return 1; } buf = malloc(BUFSIZ); if (!buf) { fclose(in); fclose(out); return 1; } while (fgets(buf, BUFSIZ, in)) fputs(buf, out); free(buf); fclose(in); fclose(out); return 0; } static int usage(int code) { fprintf(stderr, "Usage: htpasswd [-c] passwordfile username\n"); fprintf(stderr, "The -c flag creates a new file.\n"); return code; } static void interrupted(int signo) { fprintf(stderr, "Interrupted.\n"); if (isatty(STDIN_FILENO)) tcsetattr(STDIN_FILENO, TCSANOW, &saved); if (tfd != -1) unlink(tmp); exit(1); } int main(int argc, char *argv[]) { int found; FILE *tfp, *f; char user[MAX_STRING_LEN]; char pwfilename[MAX_STRING_LEN]; char line[MAX_STRING_LEN]; char l[MAX_STRING_LEN]; char w[MAX_STRING_LEN]; if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved)) { fprintf(stderr, "Cannot query TTY status, sorry: %s\n", strerror(errno)); return 1; } tfd = -1; signal(SIGINT, interrupted); if (argc == 4) { if (strcmp(argv[1], "-c")) return usage(1); tfp = fopen(argv[2], "w"); if (!tfp) { fprintf(stderr, "Could not open passwd file %s for writing.\n", argv[2]); perror("fopen"); return 1; } if (strlen(argv[2]) > (sizeof(pwfilename) - 1)) { fprintf(stderr, "%s: filename is too long\n", argv[0]); fclose(tfp); return 1; } if (strchr(argv[2], ';') || strchr(argv[2], '>')) { fprintf(stderr, "%s: filename contains an illegal character\n", argv[0]); fclose(tfp); return 1; } if (strlen(argv[3]) > (sizeof(user) - 1)) { fprintf(stderr, "%s: username is too long\n", argv[0]); fclose(tfp); return 1; } if (strchr(argv[3], ':')) { fprintf(stderr, "%s: username contains an illegal character\n", argv[0]); fclose(tfp); return 1; } printf("Adding password for %s.\n", argv[3]); add_password(argv[3], tfp); fclose(tfp); return 0; } if (argc != 3) return usage(1); tfd = mkstemp(tmp); tfp = fdopen(tfd, "w"); if (!tfp) { fprintf(stderr, "Could not open temp file.\n"); close(tfd); return 1; } if (strlen(argv[1]) > (sizeof(pwfilename) - 1)) { fprintf(stderr, "%s: filename is too long\n", argv[0]); fclose(tfp); return 1; } if (((strchr(argv[1], ';')) != NULL) || ((strchr(argv[1], '>')) != NULL)) { fprintf(stderr, "%s: filename contains an illegal character\n", argv[0]); fclose(tfp); return 1; } if (strlen(argv[2]) > (sizeof(user) - 1)) { fprintf(stderr, "%s: username is too long\n", argv[0]); fclose(tfp); return 1; } if ((strchr(argv[2], ':')) != NULL) { fprintf(stderr, "%s: username contains an illegal character\n", argv[0]); fclose(tfp); return 1; } if (!(f = fopen(argv[1], "r"))) { fprintf(stderr, "Could not open passwd file %s for reading.\n", argv[1]); fprintf(stderr, "Use -c option to create new one.\n"); fclose(tfp); return 1; } strncpy(user, argv[2], sizeof(user) - 1); user[sizeof(user)-1] = '\0'; found = 0; while (!get_line(line, MAX_STRING_LEN, f)) { if (found || (line[0] == '#') || (!line[0])) { putline(tfp, line); continue; } strcpy(l, line); getword(w, l, ':'); if (strcmp(user, w)) { putline(tfp, line); continue; } printf("Changing password for user %s.\n", user); add_password(user, tfp); found = 1; } if (!found) { printf("Adding user %s.\n", user); add_password(user, tfp); } fclose(f); fclose(tfp); if (activate_template(tmp, argv[1])) fprintf(stderr, "Failed writing to %s: %s\n", argv[1], strerror(errno)); unlink(tmp); return 0; } merecat-2.31+git20220513+ds/src/libhttpd.c000066400000000000000000004001011424066547300176330ustar00rootroot00000000000000/* libhttpd.c - HTTP protocol library ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include #include #include #include #include #include /* int64_t */ #include /* PRId64 */ #ifdef HAVE_OSRELDATE_H #include #endif #include #ifdef HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif # ifdef HAVE_NDIR_H # include # endif #endif extern char *crypt(const char *key, const char *setting); extern FILE *tempfile(void); #include "base64.h" #include "file.h" #include "libhttpd.h" #include "match.h" #include "md5.h" #include "merecat.h" #include "mmc.h" #include "ssl.h" #include "tdate_parse.h" #include "timers.h" #ifdef SHOW_SERVER_VERSION #define EXPOSED_SERVER_SOFTWARE SERVER_SOFTWARE #else #define EXPOSED_SERVER_SOFTWARE PACKAGE_NAME #endif #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO #define STDERR_FILENO 2 #endif #ifndef SHUT_WR #define SHUT_WR 1 #endif #ifdef __CYGWIN__ #define timezone _timezone #endif #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #define SETSOCKOPT(sd, level, opt) \ do { \ int val = 1; \ if (setsockopt(sd, level, opt, &val, sizeof(val)) < 0) \ syslog(LOG_CRIT, "Failed enabling %s: %s", \ #opt, strerror(errno)); \ } while (0) /* Forwards. */ static void check_options(void); static void free_httpd_server(struct httpd *hs); static int initialize_listen_socket(sockaddr_t *sa); static void add_response(struct http_conn *hc, const char *str); static void send_mime(struct http_conn *hc, int status, char *title, char *encodings, const char *extraheads, const char *type, off_t length, time_t mod); static void send_response(struct http_conn *hc, int status, char *title, const char *extraheads, char *form, char *arg); static void send_response_tail(struct http_conn *hc); static void defang(char *str, char *dfstr, int dfsize); #ifdef ERR_DIR static int send_err_file(struct http_conn *hc, int status, char *title, const char *extraheads, char *filename); #endif #ifdef AUTH_FILE static void send_authenticate(struct http_conn *hc, char *realm); static int auth_check(struct http_conn *hc, char *dir); static int auth_check2(struct http_conn *hc, char *dir); #endif #ifdef ACCESS_FILE static int access_check(struct http_conn *hc, char *dir); static int access_check2(struct http_conn *hc, char *dir); #endif static void send_dirredirect(struct http_conn *hc); static int hexit(char c); static void strdecode(char *to, char *from); #ifdef GENERATE_INDEXES static void strencode(char *to, int tosize, char *from); #endif #ifdef TILDE_MAP_1 static int tilde_map_1(struct http_conn *hc); #endif #ifdef TILDE_MAP_2 static int tilde_map_2(struct http_conn *hc); #endif static int vhost_map(struct http_conn *hc); static char *expand_symlinks(char *path, char **trailer, int no_symlink_check, int tildemapped); static char *bufgets(struct http_conn *hc); static void de_dotdot(char *file); static void init_mime(void); static void figure_mime(struct http_conn *hc); #ifdef CGI_TIMELIMIT static void cgi_kill2(arg_t arg, struct timeval *now); static void cgi_kill(arg_t arg, struct timeval *now); #endif #ifdef GENERATE_INDEXES static int ls(struct http_conn *hc); #endif static char *build_env(char *fmt, char *arg); #ifdef SERVER_NAME_LIST static char *hostname_map(char *hostname); #endif static char **make_envp(struct http_conn *hc); static char **make_argp(struct http_conn *hc); static void cgi_interpose_input(struct http_conn *hc, int wfd); static void post_post_garbage_hack(struct http_conn *hc); static void cgi_interpose_output(struct http_conn *hc, int rfd); static void cgi_child(struct http_conn *hc); static int cgi(struct http_conn *hc); static void make_log_entry(struct http_conn *hc); static int check_referer(struct http_conn *hc); static int really_check_referer(struct http_conn *hc); static int sockaddr_check(sockaddr_t *sa); static size_t sockaddr_len(sockaddr_t *sa); #ifndef HAVE_ATOLL static long long atoll(const char *str); #endif /* This global keeps track of whether we are in the main process or a ** sub-process. The reason is that httpd_send_response() can get called ** in either context; when it is called from the main process it must use ** non-blocking I/O to avoid stalling the server, but when it is called ** from a sub-process it wants to use blocking I/O so that the whole ** response definitely gets written. So, it checks this variable. A bit ** of a hack but it seems to do the right thing. */ static int sub_process = 0; static int set_cloexec(int sd) { return fcntl(sd, F_SETFD, FD_CLOEXEC); } static int clear_cloexec(int sd) { return fcntl(sd, F_SETFD, 0); } /* Set no-delay / non-blocking mode on a socket. */ int httpd_set_ndelay(int sd) { int flags, newflags; flags = fcntl(sd, F_GETFL, 0); if (-1 == flags) return -1; newflags = flags | (int)O_NDELAY; if (newflags != flags) return fcntl(sd, F_SETFL, newflags); return 0; } /* Clear no-delay / non-blocking mode on a socket. */ int httpd_clear_ndelay(int sd) { int flags, newflags; flags = fcntl(sd, F_GETFL, 0); if (-1 == flags) return -1; newflags = flags & ~(int)O_NDELAY; if (newflags != flags) return fcntl(sd, F_SETFL, newflags); return 0; } static void check_options(void) { #if defined(TILDE_MAP_1) && defined(TILDE_MAP_2) syslog(LOG_CRIT, "both TILDE_MAP_1 and TILDE_MAP_2 are defined"); exit(1); #endif } static void free_httpd_server(struct httpd *hs) { if (hs->binding_hostname) free(hs->binding_hostname); if (hs->cwd) free(hs->cwd); if (hs->cgi_pattern) free(hs->cgi_pattern); if (hs->cgi_tracker) free(hs->cgi_tracker); if (hs->charset) free(hs->charset); if (hs->url_pattern) free(hs->url_pattern); if (hs->local_pattern) free(hs->local_pattern); free(hs); } #define ENA(t) t ? "ON" : "OFF" static void httpd_greeting(struct httpd *hs, sockaddr_t *sav4, sockaddr_t *sav6) { char name[202] = { 0 }; char buf[128]; if (hs->binding_hostname) { sockaddr_t *sa; if (hs->listen4_fd != -1) sa = sav4; else sa = sav6; snprintf(name, sizeof(name), "%s, ", httpd_ntoa(sa)); } /* Port and enabled features in this server */ snprintf(buf, sizeof(buf), "port: %hu, vhost: %s, ssl: %s, php: %s, ssi: %s", hs->port, ENA(hs->vhost), ENA(hs->ctx), ENA(hs->php_cgi), ENA(hs->ssi_cgi)); syslog(LOG_NOTICE, "%s starting on %s%s", PACKAGE_STRING, name, buf); } int httpd_cgi_init(struct httpd *hs, int enabled, char *cgi_pattern, int cgi_limit) { char *cp; if (!hs) { errno = EINVAL; return -1; } if (!cgi_pattern) { hs->cgi_enabled = 0; hs->cgi_pattern = NULL; hs->cgi_limit = 0; hs->cgi_count = 0; return 0; } /* Match CGI pattern but never execute */ hs->cgi_enabled = enabled; /* Nuke any leading slashes. */ if (cgi_pattern[0] == '/') ++cgi_pattern; hs->cgi_pattern = strdup(cgi_pattern); if (!hs->cgi_pattern) { syslog(LOG_CRIT, "Failed initializing CGI: %s", strerror(errno)); return -1; } /* Nuke any leading slashes in the cgi pattern. */ while ((cp = strstr(hs->cgi_pattern, "|/"))) /* -2 for the offset, +1 for the '\0' */ memmove(cp + 1, cp + 2, strlen(cp) - 1); hs->cgi_tracker = calloc(cgi_limit, sizeof(pid_t)); hs->cgi_limit = cgi_limit; hs->cgi_count = 0; return 0; } /* ** Initialize HTTP redirects **/ int httpd_redirect_add(struct httpd *hs, int code, char *pattern, char *location) { struct http_redir *redirect; if (!hs || !pattern || !location) { errno = EINVAL; return -1; } redirect = NEW(struct http_redir, 1); if (!redirect) return -1; redirect->code = code; redirect->pattern = pattern; redirect->location = location; LIST_INSERT(redirect, hs->redirect); return 0; } void httpd_redirect_free(struct httpd *hs) { struct http_redir *redirect; LIST_FOREACH(redirect, hs->redirect) free(redirect); } /* ** Initialize HTTP locations **/ int httpd_location_add(struct httpd *hs, char *pattern, char *path) { struct http_location *loc; if (!hs || !pattern || !path) { errno = EINVAL; return -1; } loc = NEW(struct http_location, 1); if (!loc) return -1; loc->pattern = pattern; loc->path = path; LIST_INSERT(loc, hs->location); return 0; } void httpd_location_free(struct httpd *hs) { struct http_location *loc; LIST_FOREACH(loc, hs->location) free(loc); } /* Initialize listen sockets. Try v6 first because of a Linux peculiarity; ** like some other systems, it has magical v6 sockets that also listen for ** v4, but in Linux if you bind a v4 socket first then the v6 bind fails. */ int httpd_listen(struct httpd *hs, sockaddr_t *sav4, sockaddr_t *sav6) { if (!sav6) hs->listen6_fd = -1; else hs->listen6_fd = initialize_listen_socket(sav6); if (!sav4) hs->listen4_fd = -1; else hs->listen4_fd = initialize_listen_socket(sav4); /* If we didn't get any valid sockets, fail. */ if (hs->listen4_fd == -1 && hs->listen6_fd == -1) return -1; httpd_greeting(hs, sav4, sav6); return 0; } struct httpd *httpd_init(char *hostname, unsigned short port, void *ssl_ctx, char *charset, int max_age, char *cwd, int no_log, int no_symlink_check, int vhost, int global_passwd, char *url_pattern, char *local_pattern, int no_empty_referers, int list_dotfiles) { struct httpd *hs; static char ghnbuf[256]; check_options(); hs = NEW(struct httpd, 1); if (!hs) { syslog(LOG_CRIT, "out of memory allocating struct httpd"); return NULL; } if (hostname) { hs->binding_hostname = strdup(hostname); if (!hs->binding_hostname) { syslog(LOG_CRIT, "out of memory copying hostname"); free_httpd_server(hs); return NULL; } hs->server_hostname = hs->binding_hostname; } else { hs->binding_hostname = NULL; hs->server_hostname = NULL; if (gethostname(ghnbuf, sizeof(ghnbuf)) < 0) ghnbuf[0] = '\0'; #ifdef SERVER_NAME_LIST if (ghnbuf[0] != '\0') hs->server_hostname = hostname_map(ghnbuf); #endif if (!hs->server_hostname) { #ifdef SERVER_NAME hs->server_hostname = SERVER_NAME; #else if (ghnbuf[0] != '\0') hs->server_hostname = ghnbuf; #endif } } hs->port = port; hs->ctx = ssl_ctx; hs->charset = strdup(charset); hs->max_age = max_age; hs->cwd = strdup(cwd); if (!hs->cwd) { syslog(LOG_CRIT, "out of memory copying cwd"); free_httpd_server(hs); return NULL; } if (!url_pattern) { hs->url_pattern = NULL; } else { hs->url_pattern = strdup(url_pattern); if (!hs->url_pattern) { syslog(LOG_CRIT, "out of memory copying url_pattern"); free_httpd_server(hs); return NULL; } } if (!local_pattern) { hs->local_pattern = NULL; } else { hs->local_pattern = strdup(local_pattern); if (!hs->local_pattern) { syslog(LOG_CRIT, "out of memory copying local_pattern"); free_httpd_server(hs); return NULL; } } hs->no_log = no_log; hs->no_symlink_check = no_symlink_check; hs->vhost = vhost; hs->global_passwd = global_passwd; hs->no_empty_referers = no_empty_referers; hs->list_dotfiles = list_dotfiles; hs->php_cgi = php_cgi; hs->php_pattern = php_pattern; hs->ssi_cgi = ssi_cgi; hs->ssi_pattern = ssi_pattern; init_mime(); hs->listen4_fd = -1; hs->listen6_fd = -1; return hs; } static int initialize_listen_socket(sockaddr_t *sa) { int listen_fd; int flags; /* Check sockaddr. */ if (!sockaddr_check(sa)) { syslog(LOG_CRIT, "unknown sockaddr family on listen socket"); return -1; } /* Create socket. */ listen_fd = socket(sa->sa.sa_family, SOCK_STREAM, 0); if (listen_fd < 0) { syslog(LOG_CRIT, "Failed opening socket for %s: %s", httpd_ntoa(sa), strerror(errno)); return -1; } if (-1 == set_cloexec(listen_fd)) syslog(LOG_ERR, "failed setting CLOEXEC on listen socket: %s", strerror(errno)); /* Allow reuse of local addresses. */ SETSOCKOPT(listen_fd, SOL_SOCKET, SO_REUSEADDR); #ifdef SO_REUSEPORT SETSOCKOPT(listen_fd, SOL_SOCKET, SO_REUSEPORT); #endif /* Bind to it. */ if (bind(listen_fd, &sa->sa, sockaddr_len(sa)) < 0) { syslog(LOG_CRIT, "Failed binding to %s port %d: %s", httpd_ntoa(sa), httpd_port(sa), strerror(errno)); (void)close(listen_fd); return -1; } /* Set the listen file descriptor to no-delay / non-blocking mode. */ if (-1 == httpd_set_ndelay(listen_fd)) { syslog(LOG_ERR, "failed setting listen socket non-blocking: %s", strerror(errno)); (void)close(listen_fd); return -1; } /* Start a listen going. */ if (listen(listen_fd, LISTEN_BACKLOG) < 0) { syslog(LOG_CRIT, "listen: %s", strerror(errno)); (void)close(listen_fd); return -1; } /* Use accept filtering, if available. */ #ifdef SO_ACCEPTFILTER { #if (__FreeBSD_version >= 411000) #define ACCEPT_FILTER_NAME "httpready" #else #define ACCEPT_FILTER_NAME "dataready" #endif struct accept_filter_arg af; memset(&af, 0, sizeof(af)); strcpy(af.af_name, ACCEPT_FILTER_NAME); setsockopt(listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)); } #endif /* SO_ACCEPTFILTER */ return listen_fd; } void httpd_exit(struct httpd *hs) { httpd_ssl_exit(hs); httpd_unlisten(hs); httpd_redirect_free(hs); httpd_location_free(hs); free_httpd_server(hs); } void httpd_unlisten(struct httpd *hs) { if (hs->listen4_fd != -1) { (void)close(hs->listen4_fd); hs->listen4_fd = -1; } if (hs->listen6_fd != -1) { (void)close(hs->listen6_fd); hs->listen6_fd = -1; } } /* Conditional macro to allow two alternate forms for use in the built-in ** error pages. If EXPLICIT_ERROR_PAGES is defined, the second and more ** explicit error form is used; otherwise, the first and more generic ** form is used. */ #ifdef EXPLICIT_ERROR_PAGES #define ERROR_FORM(a,b) b #else #define ERROR_FORM(a,b) a #endif static char *ok200title = "OK"; static char *ok206title = "Partial Content"; static char *err301title = "Moved Permanently"; static char *err301form = "The actual URL is '%s'.\n"; static char *err302title = "Found"; static char *err302form = "The actual URL is '%s'.\n"; static char *err303title = "See Other"; static char *err303form = "The actual URL is '%s'.\n"; static char *err304title = "Not Modified"; static char *err307title = "Temporary Redirect"; static char *err307form = "The actual URL is '%s'.\n"; char *httpd_err400title = "Bad Request"; char *httpd_err400form = "Your request has bad syntax(%s) or is inherently impossible to satisfy.\n"; #ifdef AUTH_FILE static char *err401title = "Unauthorized"; static char *err401form = "Authorization required for the URL '%s'.\n"; #endif static char *err403title = "Forbidden"; #ifndef EXPLICIT_ERROR_PAGES static char *err403form = "You do not have permission to get URL '%s' from this server.\n"; #endif static char *err404title = "Not Found"; static char *err404form = "The requested URL '%s' was not found on this server.\n"; char *httpd_err408title = "Request Timeout"; char *httpd_err408form = "No request appeared within a reasonable time period.\n"; static char *err500title = "Internal Error"; static char *err500form = "There was an unusual problem serving the requested URL '%s'.\n"; static char *err501title = "Not Implemented"; static char *err501form = "The requested method '%s' is not implemented by this server.\n"; char *httpd_err503title = "Service Temporarily Overloaded"; char *httpd_err503form = "The requested URL '%s' is temporarily overloaded. Please try again later.\n"; /* Append a string to the buffer waiting to be sent as response. */ static void add_response(struct http_conn *hc, const char *str) { size_t len; len = strlen(str); httpd_realloc_str(&hc->response, &hc->maxresponse, hc->responselen + len); memmove(&(hc->response[hc->responselen]), str, len + 1); hc->responselen += len; } /* Send the buffered response. */ void httpd_send_response(struct http_conn *hc) { /* If we are in a sub-process, turn off no-delay mode. */ if (sub_process) (void)httpd_clear_ndelay(hc->conn_fd); /* Send the response, if necessary. */ if (hc->responselen > 0) { make_log_entry(hc); httpd_write(hc, hc->response, hc->responselen); hc->responselen = 0; } } static int content_encoding(struct http_conn *hc, char *encodings, char *buf, size_t len) { char *skip[] = { /* Sorted in order of most likely */ "x-tar", "octet-stream", }; size_t i; int hasenc = 0; int addgz = 0; int ret = 0; int gz; /* Skip Content-Encoding for Content-Type intended for download */ for (i = 0; i < NELEMS(skip); i++) { if (strstr(hc->type, skip[i])) return 0; } gz = hc->compression_type == COMPRESSION_GZIP; if (encodings && encodings[0]) { hasenc = 1; addgz = gz && !strstr(encodings, "gzip"); } if (hasenc) ret = snprintf(buf, len, "Content-Encoding: %s%s\r\n", encodings, addgz ? ", gzip" : ""); else if (gz) ret = snprintf(buf, len, "Content-Encoding: gzip\r\n"); return ret; } static void send_mime(struct http_conn *hc, int status, char *title, char *encodings, const char *extraheads, const char *type, off_t length, time_t mod) { time_t now; const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; char fixed_type[500]; char buf[1000]; int partial_content; int s100; if (status != 200) hc->compression_type = COMPRESSION_NONE; hc->status = status; hc->bytes_to_send = length; if (hc->mime_flag) { char nowbuf[100]; char modbuf[100]; char etagbuf[45] = { 0 }; if (status == 200 && hc->got_range && (hc->last_byte_index >= hc->first_byte_index) && ((hc->last_byte_index != length - 1) || (hc->first_byte_index != 0)) && (hc->range_if == (time_t)-1 || hc->range_if == hc->sb.st_mtime)) { partial_content = 1; hc->status = status = 206; title = ok206title; hc->compression_type = COMPRESSION_NONE; /* probably some way to get around this... */ } else { partial_content = 0; hc->got_range = 0; } now = time(NULL); if (!mod) mod = now; strftime(nowbuf, sizeof(nowbuf), rfc1123fmt, gmtime(&now)); strftime(modbuf, sizeof(modbuf), rfc1123fmt, gmtime(&mod)); snprintf(fixed_type, sizeof(fixed_type), type, hc->hs->charset); /* Match Apache as close as possible, but follow RFC 2616, section 4.2 */ snprintf(buf, sizeof(buf), "%.20s %d %s\r\n" "Date: %s\r\n" "Server: %s\r\n" "Last-Modified: %s\r\n" "Accept-Ranges: bytes\r\n", hc->protocol, status, title, nowbuf, EXPOSED_SERVER_SOFTWARE, modbuf); add_response(hc, buf); /* HTTP Strict Transport Security: https://www.chromium.org/hsts */ if (hc->ssl) { snprintf(buf, sizeof(buf), "Strict-Transport-Security: " "max-age=31536000; includeSubDomains; " "preload\r\n"); add_response(hc, buf); } if (partial_content) { snprintf(buf, sizeof(buf), "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" "Content-Length: %" PRId64 "\r\n", (int64_t)hc->first_byte_index, (int64_t)hc->last_byte_index, (int64_t)length, (int64_t)(hc->last_byte_index - hc->first_byte_index + 1)); add_response(hc, buf); } else if (length >= 0) { /* * Avoid sending Content-Length on content we * deflate or have .gz files of already. In the * former case we don't know the length yet. */ if (hc->compression_type == COMPRESSION_NONE) { snprintf(buf, sizeof(buf), "Content-Length: %" PRId64 "\r\n", (int64_t)length); add_response(hc, buf); } } else { // Experimental: Allow keep-alive also for dir listings etc. // hc->do_keep_alive = 0; } snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", fixed_type); add_response(hc, buf); if (strstr(fixed_type, "application/pdf")) { char *ptr = strrchr(hc->expnfilename, '/'); if (ptr) ptr++; else ptr = hc->expnfilename; snprintf(buf, sizeof(buf), "Content-Disposition: inline; filename=%s\r\n", ptr); add_response(hc, buf); } if (content_encoding(hc, encodings, buf, sizeof(buf))) add_response(hc, buf); s100 = status / 100; if (s100 != 2 && s100 != 3) { snprintf(buf, sizeof(buf), "Cache-Control: no-cache,no-store\r\n"); add_response(hc, buf); } /* EntityTag -- https://en.wikipedia.org/wiki/HTTP_ETag */ if (hc->file_address) { uint8_t dig[MD5_DIGEST_LENGTH]; MD5_CTX ctx; MD5Init(&ctx); MD5Update(&ctx, (const u_int8_t *)hc->file_address, length); MD5Final(dig, &ctx); snprintf(etagbuf, sizeof(etagbuf), "ETag: \"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\"\r\n", dig[0], dig[1], dig[2], dig[3], dig[4], dig[5], dig[6], dig[7], dig[8], dig[9], dig[10], dig[11], dig[12], dig[13], dig[14], dig[15]); } if (hc->hs->max_age >= 0) { if (hc->hs->max_age == 0) snprintf(buf, sizeof(buf), "Cache-Control: no-cache,no-stored\r\n"); else snprintf(buf, sizeof(buf), "Cache-Control: max-age=%d\r\n%s", hc->hs->max_age, etagbuf); add_response(hc, buf); /* Expires was superseded by Cache-Control in HTTP/1.1 */ #ifdef USE_SUPERSEDED_EXPIRES char expbuf[100]; time_t expires; /* NOTE: This header requires that the web server's clock is ** properly set, which is very unlikely for many small ** or embedded systems. */ expires = now + hc->hs->max_age; strftime(expbuf, sizeof(expbuf), rfc1123fmt, gmtime(&expires)); snprintf(buf, sizeof(buf), "Expires: %s\r\n", expbuf); add_response(hc, buf); #endif } if (hc->do_keep_alive) snprintf(buf, sizeof(buf), "Connection: keep-alive\r\n"); else snprintf(buf, sizeof(buf), "Connection: close\r\n"); add_response(hc, buf); if (extraheads[0] != '\0') add_response(hc, extraheads); add_response(hc, "\r\n"); } } static int str_alloc_count = 0; static size_t str_alloc_size = 0; void httpd_realloc_str(char **str, size_t *curr_len, size_t new_len) { if (*curr_len == 0) { *curr_len = MAX(200, new_len + 100); *str = NEW(char, *curr_len + 1); ++str_alloc_count; str_alloc_size += *curr_len; } else if (new_len > *curr_len) { str_alloc_size -= *curr_len; *curr_len = MAX(*curr_len * 2, new_len * 5 / 4); *str = RENEW(*str, char, *curr_len + 1); str_alloc_size += *curr_len; } else { return; } if (!*str) { syslog(LOG_ERR, "out of memory reallocating a string to %zu bytes", *curr_len); exit(1); } } static void send_response(struct http_conn *hc, int status, char *title, const char *extraheads, char *form, char *arg) { char defanged_arg[1000], buf[2000]; send_mime(hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) - 1, (time_t)0); snprintf(buf, sizeof(buf), "\n" "\n" " \n" " %d %s\n" " \n" "%s" " \n" " \n" "
\n" "

%d %s

\n" "

\n", status, title, httpd_css_default(), status, title); add_response(hc, buf); defang(arg, defanged_arg, sizeof(defanged_arg)); snprintf(buf, sizeof(buf), form, defanged_arg); add_response(hc, buf); #ifdef MSIE_PADDING if (match("**MSIE**", hc->useragent)) { int n; add_response(hc, "\n"); } #endif add_response(hc, "

"); send_response_tail(hc); } static char *get_hostname(struct http_conn *hc) { char *host; static char *fallback = ""; if (hc->hs->vhost && hc->hostname) host = hc->hostname; else host = hc->hs->server_hostname; if (!host) host = fallback; return host; } static void send_response_tail(struct http_conn *hc) { char buf[1000]; snprintf(buf, sizeof(buf), "
%s httpd at %s port %d
\n" "
\n" "\n" "\n", EXPOSED_SERVER_SOFTWARE, get_hostname(hc), (int)hc->hs->port); add_response(hc, buf); } static void defang(char *str, char *dfstr, int dfsize) { char *cp1; char *cp2; for (cp1 = str, cp2 = dfstr; *cp1 != '\0' && cp2 - dfstr < dfsize - 8; ++cp1, ++cp2) { switch (*cp1) { case '<': *cp2++ = '&'; *cp2++ = 'l'; *cp2++ = 't'; *cp2 = ';'; break; case '>': *cp2++ = '&'; *cp2++ = 'g'; *cp2++ = 't'; *cp2 = ';'; break; case '&': *cp2++ = '&'; *cp2++ = 'a'; *cp2++ = 'm'; *cp2++ = 'p'; *cp2 = ';'; break; case '"': *cp2++ = '&'; *cp2++ = 'q'; *cp2++ = 'u'; *cp2++ = 'o'; *cp2++ = 't'; *cp2 = ';'; break; case '\'': *cp2++ = '&'; *cp2++ = '#'; *cp2++ = '3'; *cp2++ = '9'; *cp2 = ';'; break; case '?': *cp2++ = '&'; *cp2++ = '#'; *cp2++ = '6'; *cp2++ = '3'; *cp2 = ';'; break; default: *cp2 = *cp1; break; } } *cp2 = '\0'; } void httpd_send_err(struct http_conn *hc, int status, char *title, const char *extraheads, char *form, char *arg) { #ifdef ERR_DIR char filename[1000]; /* Try virtual host error page. */ if (hc->hs->vhost && hc->hostdir[0] != '\0') { snprintf(filename, sizeof(filename), "%s/%s/err%d.html", hc->hostdir, ERR_DIR, status); if (send_err_file(hc, status, title, extraheads, filename)) return; } /* Try server-wide error page. */ snprintf(filename, sizeof(filename), "%s/err%d.html", ERR_DIR, status); if (send_err_file(hc, status, title, extraheads, filename)) return; /* Fall back on built-in error page. */ send_response(hc, status, title, extraheads, form, arg); #else send_response(hc, status, title, extraheads, form, arg); #endif } #ifdef ERR_DIR static int send_err_file(struct http_conn *hc, int status, char *title, const char *extraheads, char *filename) { FILE *fp; char buf[1000]; size_t r; fp = fopen(filename, "r"); if (!fp) return 0; send_mime(hc, status, title, "", extraheads, "text/html; charset=%s", (off_t) - 1, (time_t)0); for (;;) { r = fread(buf, 1, sizeof(buf) - 1, fp); if (r == 0) break; buf[r] = '\0'; add_response(hc, buf); } (void)fclose(fp); #ifdef ERR_APPEND_SERVER_INFO send_response_tail(hc); #endif return 1; } #endif /* ERR_DIR */ #if defined(ACCESS_FILE) || defined(AUTH_FILE) static char *find_htfile(char *topdir, char *dir, char *htfile) { int found = 0; char *path; size_t dirlen = strlen(dir); size_t len = dirlen + strlen(htfile) + 2; path = malloc(len); if (!path) return NULL; snprintf(path, len, "%s%s%s", (dir[0] ? dir : "."), dir[dirlen - 1] == '/' ? "" : "/", htfile); while (1) { int rc; char *ptr, *slash; struct stat st; rc = lstat(path, &st); ptr = strstr(path, htfile); if (!ptr) break; *--ptr = 0; if (rc == 0) { found = 1; break; } /* loop until we hit topdir. */ if (strcmp(topdir, path) == 0) break; /* Nope, try up a level. */ slash = strrchr(path, '/'); if (!slash) break; memmove(slash + 1, ptr + 1, strlen(htfile) + 1); } if (!found) { free(path); return NULL; } return path; } #endif #ifdef ACCESS_FILE /* Returns -1 == unauthorized, 0 == no access file, 1 = authorized. */ static int access_check(struct http_conn *hc, char *dir) { int rc = 0; char *topdir, *tmp = NULL; if (!dir) { char *ptr; if (strstr(hc->expnfilename, ACCESS_FILE)) { syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve access file", httpd_client(hc), hc->encodedurl); return -1; } tmp = strdup(hc->expnfilename); if (!tmp) { syslog(LOG_ERR, "out of memory in access code; " "Denying access."); return -1; } ptr = strrchr(tmp, '/'); if (!ptr) strcpy(tmp, "."); else *ptr = '\0'; dir = tmp; } if (hc->hs->vhost && hc->hostdir[0] != '\0') topdir = hc->hostdir; else topdir = "."; if (!hc->hs->global_passwd) { char *path; local: path = find_htfile(topdir, dir, ACCESS_FILE); if (path) { rc = access_check2(hc, path); free(path); } if (tmp) free(tmp); return rc; } rc = access_check2(hc, topdir); if (!rc) goto local; if (tmp) free(tmp); return rc; } /* Returns -1 == unauthorized, 0 == no access file, 1 = authorized. */ static int access_check2(struct http_conn *hc, char *dir) { struct in_addr ipv4_addr, ipv4_mask = { 0xffffffff }; FILE *fp; char line[500]; struct stat sb; char *addr, *addr1, *addr2, *mask; size_t l; /* Construct access filename. */ httpd_realloc_str(&hc->accesspath, &hc->maxaccesspath, strlen(dir) + 1 + sizeof(ACCESS_FILE)); snprintf(hc->accesspath, hc->maxaccesspath, "%s/%s", dir, ACCESS_FILE); /* Does this directory have an access file? */ if (lstat(hc->accesspath, &sb) < 0) { /* Nope, let the request go through. */ return 0; } /* Open the access file. */ fp = fopen(hc->accesspath, "r"); if (!fp) { /* The file exists but we can't open it? Disallow access. */ syslog(LOG_ERR, "%.80s access file %.80s could not be opened: %s", httpd_client(hc), hc->accesspath, strerror(errno)); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%.80s' is protected by an access file. (2)"), hc->encodedurl); return -1; } /* Read it. */ while (fgets(line, sizeof(line), fp)) { /* Nuke newline. */ l = strlen(line); if (line[l - 1] == '\n') line[l - 1] = '\0'; addr1 = strrchr(line, ' '); addr2 = strrchr(line, '\t'); if (addr1 > addr2) addr = addr1; else addr = addr2; if (!addr) { err: (void)fclose(fp); syslog(LOG_ERR, "%.80s access file %.80s: invalid line: %s", httpd_client(hc), hc->accesspath, line); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%.80s' is protected by an access file. (1)"), hc->encodedurl); return -1; } mask = strchr(++addr, '/'); if (mask) { *mask++ = '\0'; if (!*mask) goto err; if (!strchr(mask, '.')) { long l = atol(mask); if ((l < 0) || (l > 32)) goto err; for (l = 32 - l; l > 0; --l) ipv4_mask.s_addr ^= 1 << (l - 1); ipv4_mask.s_addr = htonl(ipv4_mask.s_addr); } else { if (!inet_aton(mask, &ipv4_mask)) goto err; } } if (!inet_aton(addr, &ipv4_addr)) goto err; /* * Does client addr match this rule? * TODO: Generalize and add IPv6 support */ if ((hc->client.sin.sin_addr.s_addr & ipv4_mask.s_addr) == (ipv4_addr.s_addr & ipv4_mask.s_addr)) { /* Yes. */ switch (line[0]) { case 'd': case 'D': break; case 'a': case 'A': (void)fclose(fp); return 1; default: goto err; } } } httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%.80s' is protected by an address restriction."), hc->encodedurl); (void)fclose(fp); return -1; } #endif /* ACCESS_FILE */ #ifdef AUTH_FILE static void send_authenticate(struct http_conn *hc, char *realm) { static char *header; static size_t maxheader = 0; static char headstr[] = "WWW-Authenticate: Basic realm=\""; httpd_realloc_str(&header, &maxheader, sizeof(headstr) + strlen(realm) + 3); snprintf(header, maxheader, "%s%s\"\r\n", headstr, realm); httpd_send_err(hc, 401, err401title, header, err401form, hc->encodedurl); /* If the request was a POST then there might still be data to be read, ** so we need to do a lingering close. */ if (hc->method == METHOD_POST || hc->method == METHOD_PUT) hc->should_linger = 1; } /* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */ static int auth_check(struct http_conn *hc, char *dir) { int rc = 0; char *topdir, *tmp = NULL; if (!dir) { char *ptr; if (strstr(hc->expnfilename, AUTH_FILE)) { syslog(LOG_NOTICE, "%.80s URL \"%.80s\" tried to retrieve auth file", httpd_client(hc), hc->encodedurl); return -1; } tmp = strdup(hc->expnfilename); if (!tmp) { syslog(LOG_ERR, "out of memory in authentication code; " "Denying authorization."); return -1; } ptr = strrchr(tmp, '/'); if (!ptr) strcpy(tmp, "."); else *ptr = '\0'; dir = tmp; } if (hc->hs->vhost && hc->hostdir[0] != '\0') topdir = hc->hostdir; else topdir = "."; if (!hc->hs->global_passwd) { char *path; local: path = find_htfile(topdir, dir, AUTH_FILE); if (path) { rc = auth_check2(hc, path); free(path); } if (tmp) free(tmp); return rc; } rc = auth_check2(hc, topdir); if (!rc) goto local; if (tmp) free(tmp); return rc; } /* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */ static int auth_check2(struct http_conn *hc, char *dir) { struct stat sb; char authinfo[550]; char *authpass; char *colon; int l; FILE *fp; char line[500]; char *cryp; static time_t prevmtime; char *crypt_result; /* Construct auth filename. */ httpd_realloc_str(&hc->authpath, &hc->maxauthpath, strlen(dir) + 1 + sizeof(AUTH_FILE)); snprintf(hc->authpath, hc->maxauthpath, "%s/%s", dir, AUTH_FILE); /* Does this directory have an auth file? */ if (lstat(hc->authpath, &sb) < 0) /* Nope, let the request go through. */ return 0; /* If it was a symlink, check that the target exists */ if (stat(hc->authpath, &sb) < 0) goto enoent; /* Does this request contain basic authorization info? */ if (hc->authorization[0] == '\0' || strncmp(hc->authorization, "Basic ", 6) != 0) { /* Nope, return a 401 Unauthorized. */ send_authenticate(hc, dir); return -1; } /* Decode it. */ l = b64_decode(&(hc->authorization[6]), (unsigned char *)authinfo, sizeof(authinfo) - 1); authinfo[l] = '\0'; /* Split into user and password. */ authpass = strchr(authinfo, ':'); if (!authpass) { /* No colon? Bogus auth info. */ send_authenticate(hc, dir); return -1; } *authpass++ = '\0'; /* If there are more fields, cut them off. */ colon = strchr(authpass, ':'); if (colon) *colon = '\0'; /* See if we have a cached entry and can use it. */ if (hc->maxprevauthpath != 0 && strcmp(hc->authpath, hc->prevauthpath) == 0 && sb.st_mtime == prevmtime && strcmp(authinfo, hc->prevuser) == 0) { /* Yes. Check against the cached encrypted password. */ crypt_result = crypt(authpass, hc->prevcryp); if (!crypt_result) return -1; if (strcmp(crypt_result, hc->prevcryp) == 0) { /* Ok! */ httpd_realloc_str(&hc->remoteuser, &hc->maxremoteuser, strlen(authinfo) + 1); strcpy(hc->remoteuser, authinfo); return 1; } /* No. */ send_authenticate(hc, dir); return -1; } /* Open the password file. */ fp = fopen(hc->authpath, "r"); if (!fp) { enoent: /* The file exists but we can't open it? Disallow access. */ syslog(LOG_ERR, "%.80s auth file %s could not be opened: %s", httpd_client(hc), hc->authpath, strerror(errno)); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' is protected.\n"), hc->encodedurl); return -1; } /* Read it. */ while (fgets(line, sizeof(line), fp)) { /* Nuke newline. */ l = strlen(line); if (line[l - 1] == '\n') line[l - 1] = '\0'; /* Split into user and encrypted password. */ cryp = strchr(line, ':'); if (!cryp) continue; *cryp++ = '\0'; /* Is this the right user? */ if (strcmp(line, authinfo) == 0) { /* Yes. */ (void)fclose(fp); /* So is the password right? */ crypt_result = crypt(authpass, cryp); if (!crypt_result) return -1; if (strcmp(crypt_result, cryp) == 0) { /* Ok! */ httpd_realloc_str(&hc->remoteuser, &hc->maxremoteuser, strlen(line) + 1); strcpy(hc->remoteuser, line); /* And cache this user's info for next time. */ prevmtime = sb.st_mtime; httpd_realloc_str(&hc->prevauthpath, &hc->maxprevauthpath, strlen(hc->authpath) + 1); strcpy(hc->prevauthpath, hc->authpath); httpd_realloc_str(&hc->prevuser, &hc->maxprevuser, strlen(authinfo) + 1); strcpy(hc->prevuser, authinfo); httpd_realloc_str(&hc->prevcryp, &hc->maxprevcryp, strlen(cryp) + 1); strcpy(hc->prevcryp, cryp); return 1; } /* No. */ send_authenticate(hc, dir); return -1; } } /* Didn't find that user. Access denied. */ (void)fclose(fp); send_authenticate(hc, dir); return -1; } #endif /* AUTH_FILE */ static int send_redirect(struct http_conn *hc, struct http_redir *redirect) { wordexp_t we; size_t len; char *ptr, *title, *form; switch (redirect->code) { case 301: title = err301title; form = err301form; break; case 302: title = err302title; form = err302form; break; case 303: title = err303title; form = err303form; break; case 307: title = err307title; form = err307form; break; default: return 0; } ptr = strchr(hc->hdrhost, ':'); if (ptr) *ptr = 0; setenv("host", hc->hdrhost, 1); ptr = strchr(hc->encodedurl, '?'); if (ptr) *ptr++ = 0; setenv("request_uri", hc->encodedurl, 1); if (ptr) { char query[strlen(ptr) + 2]; snprintf(query, sizeof(query), "?%s", ptr); setenv("args", query, 1); } else unsetenv("args"); if (wordexp(redirect->location, &we, 0)) return 0; len = strlen(we.we_wordv[0]) + 16; ptr = malloc(len); if (!ptr) { wordfree(&we); return 0; } snprintf(ptr, len, "Location: %s\r\n", we.we_wordv[0]); syslog(LOG_DEBUG, "Redirect %d %s", redirect->code, ptr); send_response(hc, redirect->code, title, ptr, form, we.we_wordv[0]); wordfree(&we); free(ptr); return 1; } int httpd_redirect(struct http_conn *hc) { struct http_redir *redirect; LIST_FOREACH(redirect, hc->hs->redirect) { if (!match(redirect->pattern, hc->encodedurl)) continue; /* If redirection fails we should drop the connection anyway */ if (!send_redirect(hc, redirect)) syslog(LOG_NOTICE, "Failed redirecting %s to %s, despite matching %s", hc->encodedurl, redirect->location, redirect->pattern); return 1; } return 0; } /* ** For each location match, rewrite the request to replace the leading ** match with the location path. Similar to nginx. */ int httpd_location(struct http_conn *hc, char **url) { struct http_location *loc; LIST_FOREACH(loc, hc->hs->location) { int rc; if (!loc->path) continue; rc = match(loc->pattern, hc->encodedurl); if (rc) { char *ptr = &hc->encodedurl[rc]; size_t plen, len; plen = strlen(loc->path); len = strlen(ptr) + plen + 3; *url = malloc(len); if (!*url) { syslog(LOG_ERR, "Failed allocating temporary URL rewrite buffer: %s", strerror(errno)); exit(1); } snprintf(*url, len, "%s%s%s%s", loc->path[0] != '/' ? "/" : "", loc->path, *ptr != '/' && loc->path[plen - 1] != '/' ? "/" : "", ptr); syslog(LOG_DEBUG, "Location match %s -> URL rewrite %s", loc->pattern, *url); return 1; } } return 0; } static void send_dirredirect(struct http_conn *hc) { static char *location; static char *header; static size_t maxlocation = 0, maxheader = 0; static char headstr[] = "Location: "; if (hc->query[0] != '\0') { char *cp; cp = strchr(hc->encodedurl, '?'); if (cp) /* should always find it */ *cp = '\0'; httpd_realloc_str(&location, &maxlocation, strlen(hc->encodedurl) + 2 + strlen(hc->query)); snprintf(location, maxlocation, "%s/?%s", hc->encodedurl, hc->query); } else { httpd_realloc_str(&location, &maxlocation, strlen(hc->encodedurl) + 1); snprintf(location, maxlocation, "%s/", hc->encodedurl); } httpd_realloc_str(&header, &maxheader, sizeof(headstr) + strlen(location)); snprintf(header, maxheader, "%s%s\r\n", headstr, location); send_response(hc, 302, err302title, header, err302form, location); } char *httpd_method_str(int method) { switch (method) { case METHOD_GET: return "GET"; case METHOD_HEAD: return "HEAD"; case METHOD_POST: return "POST"; case METHOD_PUT: return "PUT"; case METHOD_DELETE: return "DELETE"; case METHOD_CONNECT: return "CONNECT"; case METHOD_OPTIONS: return "OPTIONS"; case METHOD_TRACE: return "TRACE"; default: return "UNKNOWN"; } } static int hexit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return 0; /* shouldn't happen, we're guarded by isxdigit() */ } /* Copies and decodes a string. It's ok for from and to to be the ** same string. */ static void strdecode(char *to, char *from) { for (; *from != '\0'; ++to, ++from) { if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { *to = hexit(from[1]) * 16 + hexit(from[2]); from += 2; } else { *to = *from; } } *to = '\0'; } #ifdef GENERATE_INDEXES /* Copies and encodes a string. */ static void strencode(char *to, int tosize, char *from) { int tolen; for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) { if (isalnum(*from) || strchr("/_.-~", *from)) { *to = *from; ++to; ++tolen; } else { sprintf(to, "%%%02x", (int)*from & 0xff); to += 3; tolen += 3; } } *to = '\0'; } #endif /* GENERATE_INDEXES */ #ifdef TILDE_MAP_1 /* Map a ~username/whatever URL into /username. */ static int tilde_map_1(struct http_conn *hc) { static char *temp; static size_t maxtemp = 0; static char *prefix = TILDE_MAP_1; size_t len; httpd_realloc_str(&temp, &maxtemp, strlen(hc->expnfilename)); strlcpy(temp, &hc->expnfilename[1], maxtemp); len = strlen(prefix) + 2 + maxtemp; httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, len); strlcpy(hc->expnfilename, prefix, hc->maxexpnfilename); if (prefix[0] != '\0') strlcat(hc->expnfilename, "/", hc->maxexpnfilename); strlcat(hc->expnfilename, temp, hc->maxexpnfilename); return 1; } #endif /* TILDE_MAP_1 */ #ifdef TILDE_MAP_2 /* Map a ~username/whatever URL into /. */ static int tilde_map_2(struct http_conn *hc) { static char *temp; static size_t maxtemp = 0; static char *postfix = TILDE_MAP_2; struct passwd *pw; size_t len; char *rest; char *alt; char *cp; /* Get the username. */ httpd_realloc_str(&temp, &maxtemp, strlen(hc->expnfilename)); strlcpy(temp, &hc->expnfilename[1], maxtemp); cp = strchr(temp, '/'); if (cp) *cp++ = '\0'; else cp = ""; /* Get the passwd entry. */ pw = getpwnam(temp); if (!pw) return 0; /* Set up altdir. */ len = strlen(pw->pw_dir) + 2 + strlen(postfix); httpd_realloc_str(&hc->altdir, &hc->maxaltdir, len); strlcpy(hc->altdir, pw->pw_dir, hc->maxaltdir); if (postfix[0] != '\0') { strlcat(hc->altdir, "/", hc->maxaltdir); strlcat(hc->altdir, postfix, hc->maxaltdir); } alt = expand_symlinks(hc->altdir, &rest, 0, 1); if (rest[0] != '\0') return 0; httpd_realloc_str(&hc->altdir, &hc->maxaltdir, strlen(alt) + 1); strlcpy(hc->altdir, alt, hc->maxaltdir); /* And the filename becomes altdir plus the post-~ part of the original. */ len = strlen(hc->altdir) + 2 + strlen(cp); httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, len); snprintf(hc->expnfilename, hc->maxexpnfilename, "%s/%s", hc->altdir, cp); /* For this type of tilde mapping, we want to defeat vhost mapping. */ hc->tildemapped = 1; return 1; } #endif /* TILDE_MAP_2 */ /* * Allow vhosts to share top-level icons/ and cgi-bin/ */ static int is_vhost_shared(char *path) { int i; char *shared[] = { "icons/", "cgi-bin/", NULL }; if (!path || path[0] == 0) return 0; for (i = 0; shared[i]; i++) { if (!strncmp(path, shared[i], strlen(shared[i]))) return 1; } return 0; } /* Virtual host mapping. */ static int vhost_map(struct http_conn *hc) { sockaddr_t sa; socklen_t sz; char *cp1, *temp; size_t len; #ifdef VHOST_DIRLEVELS int i; char *cp2; #endif /* Figure out the virtual hostname. */ if (hc->reqhost[0] != '\0') { hc->hostname = hc->reqhost; } else if (hc->hdrhost[0] != '\0') { hc->hostname = hc->hdrhost; } else { sz = sizeof(sa); if (getsockname(hc->conn_fd, &sa.sa, &sz) < 0) { syslog(LOG_ERR, "getsockname: %s", strerror(errno)); return 0; } hc->hostname = httpd_ntoa(&sa); } /* Pound it to lower case. */ for (cp1 = hc->hostname; *cp1 != '\0'; ++cp1) if (isupper(*cp1)) *cp1 = tolower(*cp1); if (hc->tildemapped) return 1; /* Figure out the host directory. */ #ifdef VHOST_DIRLEVELS httpd_realloc_str(&hc->hostdir, &hc->maxhostdir, strlen(hc->hostname) + 2 * VHOST_DIRLEVELS); if (strncmp(hc->hostname, "www.", 4) == 0) cp1 = &hc->hostname[4]; else cp1 = hc->hostname; for (cp2 = hc->hostdir, i = 0; i < VHOST_DIRLEVELS; ++i) { /* Skip dots in the hostname. If we don't, then we get vhost ** directories in higher level of filestructure if dot gets ** involved into path construction. It's `while' used here instead ** of `if' for it's possible to have a hostname formed with two ** dots at the end of it. */ while (*cp1 == '.') ++cp1; /* Copy a character from the hostname, or '_' if we ran out. */ if (*cp1 != '\0') *cp2++ = *cp1++; else *cp2++ = '_'; /* Copy a slash. */ *cp2++ = '/'; } strcpy(cp2, hc->hostname); #else /* VHOST_DIRLEVELS */ httpd_realloc_str(&hc->hostdir, &hc->maxhostdir, strlen(hc->hostname) + 1); strlcpy(hc->hostdir, hc->hostname, hc->maxhostdir); #endif /* VHOST_DIRLEVELS */ /* Prepend hostdir to the filename. */ len = strlen(hc->expnfilename); temp = strdup(hc->expnfilename); httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(hc->hostdir) + 2 + len); strlcpy(hc->expnfilename, hc->hostdir, hc->maxexpnfilename); /* Skip any port number */ cp1 = strrchr(hc->expnfilename, ':'); if (cp1) *cp1 = 0; strlcat(hc->expnfilename, "/", hc->maxexpnfilename); strlcat(hc->expnfilename, temp, hc->maxexpnfilename); free(temp); return 1; } /* Expands all symlinks in the given filename, eliding ..'s and leading ** /'s. Returns the expanded path (pointer to static string), or NULL ** on errors. Also returns, in the string pointed to by trailer, any ** trailing parts of the path that don't exist. ** ** This is a fairly nice little routine. It handles any size filenames ** without excessive mallocs. */ static char *expand_symlinks(char *path, char **trailer, int no_symlink_check, int tildemapped) { static char *checked; static char *rest; char link[5000]; static size_t maxchecked = 0, maxrest = 0; size_t checkedlen, restlen, prevcheckedlen, prevrestlen; ssize_t linklen; int nlinks, i; char *r; char *cp1; char *cp2; if (no_symlink_check) { /* If we are chrooted, we can actually skip the symlink-expansion, ** since it's impossible to get out of the tree. However, we still ** need to do the pathinfo check, and the existing symlink expansion ** code is a pretty reasonable way to do this. So, what we do is ** a single stat() of the whole filename - if it exists, then we ** return it as is with nothing in trailer. If it doesn't exist, we ** fall through to the existing code. ** ** One side-effect of this is that users can't symlink to central ** approved CGIs any more. The workaround is to use the central ** URL for the CGI instead of a local symlinked one. */ struct stat sb; if (stat(path, &sb) != -1) { checkedlen = strlen(path) + 1; httpd_realloc_str(&checked, &maxchecked, checkedlen); strlcpy(checked, path, maxchecked); /* Trim trailing slashes. */ while (checkedlen && checked[checkedlen - 1] == '/') { checked[checkedlen - 1] = '\0'; --checkedlen; } httpd_realloc_str(&rest, &maxrest, 0); rest[0] = '\0'; *trailer = rest; return checked; } } /* Start out with nothing in checked and the whole filename in rest. */ httpd_realloc_str(&checked, &maxchecked, 1); checked[0] = '\0'; checkedlen = 0; restlen = strlen(path) + 1; httpd_realloc_str(&rest, &maxrest, restlen); memset(rest, 0, maxrest); strlcpy(rest, path, maxrest); if (!tildemapped) { /* Remove any leading slashes. */ while (restlen && rest[0] == '/') { /* One more for '\0', one less for the eaten first */ memmove(rest, &(rest[1]), strlen(rest)); --restlen; } } r = rest; nlinks = 0; /* While there are still components to check... */ while (restlen > 0) { /* Save current checkedlen in case we get a symlink. Save current ** restlen in case we get a non-existant component. */ prevcheckedlen = checkedlen; prevrestlen = restlen; /* Grab one component from r and transfer it to checked. */ cp1 = strchr(r, '/'); if (cp1) { i = cp1 - r; if (i == 0) { /* Special case for absolute paths. */ httpd_realloc_str(&checked, &maxchecked, checkedlen + 1); strncpy(&checked[checkedlen], r, 1); checkedlen += 1; } else if (strncmp(r, "..", MAX(i, 2)) == 0) { /* Ignore ..'s that go above the start of the path. */ if (checkedlen != 0) { cp2 = strrchr(checked, '/'); if (!cp2) checkedlen = 0; else if (cp2 == checked) checkedlen = 1; else checkedlen = cp2 - checked; } } else { httpd_realloc_str(&checked, &maxchecked, checkedlen + 1 + i); if (checkedlen > 0 && checked[checkedlen - 1] != '/') checked[checkedlen++] = '/'; strncpy(&checked[checkedlen], r, i); checkedlen += i; } checked[checkedlen] = '\0'; r += i + 1; restlen -= i + 1; } else { /* No slashes remaining, r is all one component. */ if (strcmp(r, "..") == 0) { /* Ignore ..'s that go above the start of the path. */ if (checkedlen != 0) { cp2 = strrchr(checked, '/'); if (!cp2) checkedlen = 0; else if (cp2 == checked) checkedlen = 1; else checkedlen = cp2 - checked; checked[checkedlen] = '\0'; } } else { httpd_realloc_str(&checked, &maxchecked, checkedlen + 2 + restlen); if (checkedlen > 0 && checked[checkedlen - 1] != '/') strlcat(checked, "/", maxchecked); strlcat(checked, r, maxchecked); checkedlen = strlen(checked); } r += restlen; restlen = 0; } /* Try reading the current filename as a symlink */ if (checked[0] == '\0') continue; linklen = readlink(checked, link, sizeof(link) - 1); if (linklen == -1) { if (errno == EINVAL) continue; /* not a symlink */ if (errno == EACCES || errno == ENOENT || errno == ENOTDIR) { /* That last component was bogus. Restore and return. */ *trailer = r - (prevrestlen - restlen); if (prevcheckedlen == 0) strcpy(checked, "."); else checked[prevcheckedlen] = '\0'; return checked; } syslog(LOG_ERR, "readlink %s: %s", checked, strerror(errno)); return NULL; } ++nlinks; if (nlinks > MAX_LINKS) { syslog(LOG_ERR, "too many symlinks in %s", path); return NULL; } link[linklen] = '\0'; if (link[linklen - 1] == '/') link[--linklen] = '\0'; /* trim trailing slash */ /* Insert the link contents in front of the rest of the filename. */ if (restlen != 0) { memmove(rest, r, strlen(r) + 1); httpd_realloc_str(&rest, &maxrest, restlen + linklen + 1); for (i = restlen; i >= 0; --i) rest[i + linklen + 1] = rest[i]; memmove(rest, link, strlen(link) + 1); rest[linklen] = '/'; restlen += linklen + 1; r = rest; } else { /* There's nothing left in the filename, so the link contents ** becomes the rest. */ httpd_realloc_str(&rest, &maxrest, linklen); memmove(rest, link, strlen(link) + 1); restlen = linklen; r = rest; } if (rest[0] == '/') { /* There must have been an absolute symlink - zero out checked. */ checked[0] = '\0'; checkedlen = 0; } else { /* Re-check this component. */ checkedlen = prevcheckedlen; checked[checkedlen] = '\0'; } } *trailer = r; if (checked[0] == '\0') strcpy(checked, "."); return checked; } void httpd_close_conn(struct http_conn *hc, struct timeval *now) { if (hc->file_address) { mmc_unmap(hc->file_address, &(hc->sb), now); hc->file_address = NULL; } if (hc->conn_fd >= 0) { httpd_ssl_close(hc); (void)close(hc->conn_fd); hc->conn_fd = -1; } } void httpd_destroy_conn(struct http_conn *hc) { if (hc->initialized) { free(hc->read_buf); free(hc->decodedurl); free(hc->origfilename); free(hc->indexname); free(hc->expnfilename); free(hc->encodings); free(hc->pathinfo); free(hc->query); free(hc->accept); free(hc->accepte); free(hc->reqhost); free(hc->hostdir); free(hc->remoteuser); free(hc->response); #ifdef TILDE_MAP_2 free(hc->altdir); #endif #ifdef ACCESS_FILE free(hc->accesspath); #endif #ifdef AUTH_FILE free(hc->authpath); free(hc->prevauthpath); free(hc->prevuser); free(hc->prevcryp); #endif httpd_ssl_shutdown(hc); hc->initialized = 0; } } void httpd_init_conn_mem(struct http_conn *hc) { if (hc->initialized) return; hc->read_size = 0; httpd_realloc_str(&hc->read_buf, &hc->read_size, 16384); hc->maxdecodedurl = hc->maxorigfilename = hc->maxindexname = hc->maxexpnfilename = hc->maxencodings = hc->maxpathinfo = hc->maxquery = hc->maxaccept = hc->maxaccepte = hc->maxreqhost = hc->maxhostdir = hc->maxremoteuser = hc->maxresponse = 0; httpd_realloc_str(&hc->decodedurl, &hc->maxdecodedurl, 1); httpd_realloc_str(&hc->origfilename, &hc->maxorigfilename, 1); httpd_realloc_str(&hc->indexname, &hc->maxindexname, 1); httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, 0); httpd_realloc_str(&hc->encodings, &hc->maxencodings, 1); httpd_realloc_str(&hc->pathinfo, &hc->maxpathinfo, 0); httpd_realloc_str(&hc->query, &hc->maxquery, 0); httpd_realloc_str(&hc->accept, &hc->maxaccept, 0); httpd_realloc_str(&hc->accepte, &hc->maxaccepte, 0); httpd_realloc_str(&hc->reqhost, &hc->maxreqhost, 0); httpd_realloc_str(&hc->hostdir, &hc->maxhostdir, 0); httpd_realloc_str(&hc->remoteuser, &hc->maxremoteuser, 0); httpd_realloc_str(&hc->response, &hc->maxresponse, 0); #ifdef TILDE_MAP_2 hc->maxaltdir = 0; httpd_realloc_str(&hc->altdir, &hc->maxaltdir, 0); #endif #ifdef ACCESS_FILE hc->maxaccesspath = 0; httpd_realloc_str(&hc->accesspath, &hc->maxaccesspath, 0); #endif #ifdef AUTH_FILE hc->maxauthpath = 0; hc->maxprevauthpath = 0; hc->maxprevuser = 0; hc->maxprevcryp = 0; httpd_realloc_str(&hc->authpath, &hc->maxauthpath, 0); httpd_realloc_str(&hc->prevauthpath, &hc->maxprevauthpath, 0); httpd_realloc_str(&hc->prevuser, &hc->maxprevuser, 0); httpd_realloc_str(&hc->prevcryp, &hc->maxprevcryp, 0); #endif hc->initialized = 1; } void httpd_init_conn_content(struct http_conn *hc) { hc->skip_redirect = 0; hc->read_idx = 0; hc->checked_idx = 0; hc->checked_state = CHST_FIRSTWORD; hc->method = METHOD_UNKNOWN; hc->status = 0; hc->bytes_to_send = 0; hc->bytes_sent = 0; hc->encodedurl = ""; hc->decodedurl[0] = '\0'; hc->protocol = "UNKNOWN"; hc->origfilename[0] = '\0'; hc->expnfilename[0] = '\0'; hc->encodings[0] = '\0'; hc->pathinfo[0] = '\0'; hc->query[0] = '\0'; hc->referer = ""; hc->useragent = ""; hc->accept[0] = '\0'; hc->accepte[0] = '\0'; hc->acceptl = ""; hc->cookie = ""; hc->contenttype = ""; hc->reqhost[0] = '\0'; hc->hdrhost = ""; hc->hostdir[0] = '\0'; hc->authorization = ""; hc->remoteuser[0] = '\0'; hc->response[0] = '\0'; #ifdef TILDE_MAP_2 hc->altdir[0] = '\0'; #endif hc->responselen = 0; hc->if_modified_since = (time_t)-1; hc->range_if = (time_t)-1; hc->contentlength = 0; hc->type = ""; hc->hostname = NULL; hc->mime_flag = 1; hc->one_one = 0; hc->got_range = 0; hc->tildemapped = 0; hc->first_byte_index = 0; hc->last_byte_index = -1; hc->keep_alive = 0; hc->do_keep_alive = 0; hc->should_linger = 0; hc->file_address = NULL; hc->compression_type = COMPRESSION_NONE; } int httpd_get_conn(struct httpd *hs, int listen_fd, struct http_conn *hc) { sockaddr_t sa; socklen_t sz; char *address; httpd_init_conn_mem(hc); /* Accept the new connection. */ sz = sizeof(sa); hc->conn_fd = accept(listen_fd, &sa.sa, &sz); if (hc->conn_fd < 0) { if (errno == EWOULDBLOCK) return GC_NO_MORE; syslog(LOG_ERR, "accept: %s", strerror(errno)); return GC_FAIL; } if (!sockaddr_check(&sa)) { syslog(LOG_ERR, "unknown sockaddr family"); goto error; } if (-1 == set_cloexec(hc->conn_fd)) syslog(LOG_ERR, "failed setting CLOEXEC on client socket: %s", strerror(errno)); hc->hs = hs; memset(&hc->client, 0, sizeof(hc->client)); memcpy(&hc->client, &sa, sizeof(sa)); /* * Slightly ugly workaround to handle X-Forwarded-For better for IPv6 * Idea from https://blog.steve.fi/IPv6_and_thttpd.html */ address = httpd_ntoa(&hc->client); memset(hc->client.address, 0, sizeof(hc->client.address)); strlcpy(hc->client.address, address, sizeof(hc->client.address)); if (httpd_ssl_open(hc)) { if (hc->errmsg) syslog(LOG_INFO, "%.80s: failed HTTPS connection: %s.", httpd_client(hc), hc->errmsg); goto error; } httpd_init_conn_content(hc); return GC_OK; error: // httpd_ssl_log_errors(); (void)close(hc->conn_fd); hc->conn_fd = -1; return GC_FAIL; } /* Checks hc->read_buf to see whether a complete request has been read so far; ** either the first line has two words (an HTTP/0.9 request), or the first ** line has three words and there's a blank line present. ** ** hc->read_idx is how much has been read in; hc->checked_idx is how much we ** have checked so far; and hc->checked_state is the current state of the ** finite state machine. */ int httpd_got_request(struct http_conn *hc) { char c; for (; hc->checked_idx < hc->read_idx; ++hc->checked_idx) { c = hc->read_buf[hc->checked_idx]; switch (hc->checked_state) { case CHST_FIRSTWORD: switch (c) { case ' ': case '\t': hc->checked_state = CHST_FIRSTWS; break; case '\n': case '\r': hc->checked_state = CHST_BOGUS; return GR_BAD_REQUEST; } break; case CHST_FIRSTWS: switch (c) { case ' ': case '\t': break; case '\n': case '\r': hc->checked_state = CHST_BOGUS; return GR_BAD_REQUEST; default: hc->checked_state = CHST_SECONDWORD; break; } break; case CHST_SECONDWORD: switch (c) { case ' ': case '\t': hc->checked_state = CHST_SECONDWS; break; case '\n': case '\r': /* The first line has only two words - an HTTP/0.9 request. */ return GR_GOT_REQUEST; } break; case CHST_SECONDWS: switch (c) { case ' ': case '\t': break; case '\n': case '\r': hc->checked_state = CHST_BOGUS; return GR_BAD_REQUEST; default: hc->checked_state = CHST_THIRDWORD; break; } break; case CHST_THIRDWORD: switch (c) { case ' ': case '\t': hc->checked_state = CHST_THIRDWS; break; case '\n': hc->checked_state = CHST_LF; break; case '\r': hc->checked_state = CHST_CR; break; } break; case CHST_THIRDWS: switch (c) { case ' ': case '\t': break; case '\n': hc->checked_state = CHST_LF; break; case '\r': hc->checked_state = CHST_CR; break; default: hc->checked_state = CHST_BOGUS; return GR_BAD_REQUEST; } break; case CHST_LINE: switch (c) { case '\n': hc->checked_state = CHST_LF; break; case '\r': hc->checked_state = CHST_CR; break; } break; case CHST_LF: switch (c) { case '\n': /* Two newlines in a row - a blank line - end of request. */ return GR_GOT_REQUEST; case '\r': hc->checked_state = CHST_CR; break; default: hc->checked_state = CHST_LINE; break; } break; case CHST_CR: switch (c) { case '\n': hc->checked_state = CHST_CRLF; break; case '\r': /* Two returns in a row - end of request. */ return GR_GOT_REQUEST; default: hc->checked_state = CHST_LINE; break; } break; case CHST_CRLF: switch (c) { case '\n': /* Two newlines in a row - end of request. */ return GR_GOT_REQUEST; case '\r': hc->checked_state = CHST_CRLFCR; break; default: hc->checked_state = CHST_LINE; break; } break; case CHST_CRLFCR: switch (c) { case '\n': case '\r': /* Two CRLFs or two CRs in a row - end of request. */ return GR_GOT_REQUEST; default: hc->checked_state = CHST_LINE; break; } break; case CHST_BOGUS: return GR_BAD_REQUEST; } } return GR_NO_REQUEST; } int httpd_parse_request(struct http_conn *hc) { size_t len; char *buf; char *method_str; char *url, *url_proto; char *protocol; char *reqhost; char *eol; char *cp; char *pi; hc->checked_idx = 0; /* reset */ method_str = bufgets(hc); url = strpbrk(method_str, " \t\n\r"); if (!url) { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "1"); return -1; } *url++ = '\0'; url += strspn(url, " \t\n\r"); protocol = strpbrk(url, " \t\n\r"); if (!protocol) { protocol = "HTTP/0.9"; hc->mime_flag = 0; } else { *protocol++ = '\0'; protocol += strspn(protocol, " \t\n\r"); if (*protocol != '\0') { eol = strpbrk(protocol, " \t\n\r"); if (eol) *eol = '\0'; if (strcasecmp(protocol, "HTTP/1.0") != 0) hc->one_one = 1; } } hc->protocol = protocol; if (hc->ssl) url_proto = "https://"; else url_proto = "http://"; /* Check for HTTP/1.1 absolute URL. */ if (strncasecmp(url, url_proto, strlen(url_proto)) == 0) { if (!hc->one_one) { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "2"); return -1; } reqhost = url + strlen(url_proto); url = strchr(reqhost, '/'); if (!url) { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "3"); return -1; } *url = '\0'; if (strchr(reqhost, '/') || reqhost[0] == '.') { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "4"); return -1; } httpd_realloc_str(&hc->reqhost, &hc->maxreqhost, strlen(reqhost)); strlcpy(hc->reqhost, reqhost, hc->maxreqhost); *url = '/'; } if (*url != '/') { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "5"); return -1; } if (strcasecmp(method_str, httpd_method_str(METHOD_GET)) == 0) hc->method = METHOD_GET; else if (strcasecmp(method_str, httpd_method_str(METHOD_HEAD)) == 0) hc->method = METHOD_HEAD; else if (strcasecmp(method_str, httpd_method_str(METHOD_POST)) == 0) hc->method = METHOD_POST; else if (strcasecmp(method_str, httpd_method_str(METHOD_PUT)) == 0) hc->method = METHOD_PUT; else if (strcasecmp(method_str, httpd_method_str(METHOD_DELETE)) == 0) hc->method = METHOD_DELETE; else if (strcasecmp(method_str, httpd_method_str(METHOD_CONNECT)) == 0) hc->method = METHOD_CONNECT; else if (strcasecmp(method_str, httpd_method_str(METHOD_OPTIONS)) == 0) hc->method = METHOD_OPTIONS; else if (strcasecmp(method_str, httpd_method_str(METHOD_TRACE)) == 0) hc->method = METHOD_TRACE; else { httpd_send_err(hc, 501, err501title, "", err501form, method_str); return -1; } hc->encodedurl = url; if (httpd_location(hc, &cp)) { hc->skip_redirect = 1; httpd_realloc_str(&hc->decodedurl, &hc->maxdecodedurl, strlen(cp) + 1); strdecode(hc->decodedurl, cp); free(cp); } else { httpd_realloc_str(&hc->decodedurl, &hc->maxdecodedurl, strlen(hc->encodedurl) + 1); strdecode(hc->decodedurl, hc->encodedurl); } httpd_realloc_str(&hc->origfilename, &hc->maxorigfilename, strlen(hc->decodedurl)); strlcpy(hc->origfilename, &hc->decodedurl[1], hc->maxorigfilename); /* Special case for top-level URL. */ if (hc->origfilename[0] == '\0') strcpy(hc->origfilename, "."); /* Extract query string from encoded URL. */ cp = strchr(hc->encodedurl, '?'); if (cp) { ++cp; httpd_realloc_str(&hc->query, &hc->maxquery, strlen(cp) + 1); strlcpy(hc->query, cp, hc->maxquery); /* Remove query from (decoded) origfilename. */ cp = strchr(hc->origfilename, '?'); if (cp) *cp = '\0'; } de_dotdot(hc->origfilename); if (hc->origfilename[0] == '/' || (hc->origfilename[0] == '.' && hc->origfilename[1] == '.' && (hc->origfilename[2] == '\0' || hc->origfilename[2] == '/'))) { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "6"); return -1; } if (hc->mime_flag) { /* Read the MIME headers. */ while ((buf = bufgets(hc))) { if (buf[0] == '\0') break; if (strncasecmp(buf, "Referer:", 8) == 0) { cp = &buf[8]; cp += strspn(cp, " \t"); hc->referer = cp; } else if (strncasecmp(buf, "User-Agent:", 11) == 0) { cp = &buf[11]; cp += strspn(cp, " \t"); hc->useragent = cp; } else if (strncasecmp(buf, "Host:", 5) == 0) { cp = &buf[5]; cp += strspn(cp, " \t"); hc->hdrhost = cp; if (strchr(hc->hdrhost, '/') || hc->hdrhost[0] == '.') { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "7"); return -1; } } else if (strncasecmp(buf, "Accept:", 7) == 0) { cp = &buf[7]; cp += strspn(cp, " \t"); if (hc->accept[0] != '\0') { if (strlen(hc->accept) > 5000) { syslog(LOG_ERR, "%.80s way too much Accept: data", httpd_client(hc)); continue; } len = strlen(hc->accept) + 2 + strlen(cp); httpd_realloc_str(&hc->accept, &hc->maxaccept, len); strlcat(hc->accept, ", ", hc->maxaccept); } else httpd_realloc_str(&hc->accept, &hc->maxaccept, strlen(cp) + 1); strlcat(hc->accept, cp, hc->maxaccept); } else if (strncasecmp(buf, "Accept-Encoding:", 16) == 0) { cp = &buf[16]; cp += strspn(cp, " \t"); if (hc->accepte[0] != '\0') { if (strlen(hc->accepte) > 5000) { syslog(LOG_ERR, "%.80s way too much Accept-Encoding: data", httpd_client(hc)); continue; } len = strlen(hc->accepte) + 2 + strlen(cp); httpd_realloc_str(&hc->accepte, &hc->maxaccepte, len); strlcat(hc->accepte, ", ", hc->maxaccepte); } else { httpd_realloc_str(&hc->accepte, &hc->maxaccepte, strlen(cp) + 1); } strlcpy(hc->accepte, cp, hc->maxaccepte); } else if (strncasecmp(buf, "Accept-Language:", 16) == 0) { cp = &buf[16]; cp += strspn(cp, " \t"); hc->acceptl = cp; } else if (strncasecmp(buf, "If-Modified-Since:", 18) == 0) { cp = &buf[18]; hc->if_modified_since = tdate_parse(cp); if (hc->if_modified_since == (time_t)-1) syslog(LOG_DEBUG, "unparsable time: %s", cp); } else if (strncasecmp(buf, "Cookie:", 7) == 0) { cp = &buf[7]; cp += strspn(cp, " \t"); hc->cookie = cp; } else if (strncasecmp(buf, "Range:", 6) == 0) { /* Only support %d- and %d-%d, not %d-%d,%d-%d or -%d. */ if (!strchr(buf, ',')) { char *cp_dash; cp = strpbrk(buf, "="); if (cp) { cp_dash = strchr(cp + 1, '-'); if (cp_dash && cp_dash != cp + 1) { *cp_dash = '\0'; hc->got_range = 1; hc->first_byte_index = atoll(cp + 1); if (hc->first_byte_index < 0) hc->first_byte_index = 0; if (isdigit((int)cp_dash[1])) { hc->last_byte_index = atoll(cp_dash + 1); if (hc->last_byte_index < 0) hc->last_byte_index = -1; } } } } } else if (strncasecmp(buf, "Range-If:", 9) == 0 || strncasecmp(buf, "If-Range:", 9) == 0) { cp = &buf[9]; hc->range_if = tdate_parse(cp); if (hc->range_if == (time_t)-1) syslog(LOG_DEBUG, "unparsable time: %s", cp); } else if (strncasecmp(buf, "Content-Type:", 13) == 0) { cp = &buf[13]; cp += strspn(cp, " \t"); hc->contenttype = cp; } else if (strncasecmp(buf, "Content-Length:", 15) == 0) { cp = &buf[15]; hc->contentlength = (size_t)atol(cp); } else if (strncasecmp(buf, "Authorization:", 14) == 0) { cp = &buf[14]; cp += strspn(cp, " \t"); hc->authorization = cp; } else if (strncasecmp(buf, "Connection:", 11) == 0) { cp = &buf[11]; cp += strspn(cp, " \t"); if (strcasecmp(cp, "keep-alive") == 0) { hc->keep_alive = 1; /* Client signaling */ hc->do_keep_alive = 10; /* Our intention, which might change later */ } } else if (strncasecmp(buf, "X-Forwarded-For:", 16) == 0) { sockaddr_t sa; char *client; /* Syntax: X-Forwarded-For: client[, proxy1, proxy2, ...] */ cp = &buf[16]; cp += strspn(cp, " \t"); client = cp; cp = strstr(cp, ", "); if (cp) { *cp = 0; cp += 2; /* Skip first entry if localhost, likely Squid proxy */ if (!strcmp(client, "127.0.0.1")) client = cp; /* Skip first entry if 'unknown', likely masquerading proxy */ if (!strcmp(client, "unknown")) client = cp; } if (-1 == httpd_aton(client, &sa)) { syslog(LOG_WARNING, "%.80s: invalid X-Forwarded-For: %s", httpd_client(hc), client); httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "X-Forwarded-For"); return -1; } strlcpy(hc->client.address, sa.address, sizeof(hc->client.address)); } /* * Possibly add support for X-Real-IP: here? * http://distinctplace.com/infrastructure/2014/04/23/story-behind-x-forwarded-for-and-x-real-ip-headers/ */ #ifdef LOG_UNKNOWN_HEADERS else if (strncasecmp(buf, "Accept-Charset:", 15) == 0 || strncasecmp(buf, "Accept-Language:", 16) == 0 || strncasecmp(buf, "Agent:", 6) == 0 || strncasecmp(buf, "Cache-Control:", 14) == 0 || strncasecmp(buf, "Cache-Info:", 11) == 0 || strncasecmp(buf, "Charge-To:", 10) == 0 || strncasecmp(buf, "Client-IP:", 10) == 0 || strncasecmp(buf, "Date:", 5) == 0 || strncasecmp(buf, "Extension:", 10) == 0 || strncasecmp(buf, "Forwarded:", 10) == 0 || strncasecmp(buf, "From:", 5) == 0 || strncasecmp(buf, "HTTP-Version:", 13) == 0 || strncasecmp(buf, "Max-Forwards:", 13) == 0 || strncasecmp(buf, "Message-Id:", 11) == 0 || strncasecmp(buf, "MIME-Version:", 13) == 0 || strncasecmp(buf, "Negotiate:", 10) == 0 || strncasecmp(buf, "Pragma:", 7) == 0 || strncasecmp(buf, "Proxy-Agent:", 12) == 0 || strncasecmp(buf, "Proxy-Connection:", 17) == 0 || strncasecmp(buf, "Security-Scheme:", 16) == 0 || strncasecmp(buf, "Session-Id:", 11) == 0 || strncasecmp(buf, "UA-Color:", 9) == 0 || strncasecmp(buf, "UA-CPU:", 7) == 0 || strncasecmp(buf, "UA-Disp:", 8) == 0 || strncasecmp(buf, "UA-OS:", 6) == 0 || strncasecmp(buf, "UA-Pixels:", 10) == 0 || strncasecmp(buf, "User:", 5) == 0 || strncasecmp(buf, "Via:", 4) == 0 || strncasecmp(buf, "X-", 2) == 0) { ; /* ignore */ } else { syslog(LOG_DEBUG, "unknown request header: %s", buf); } #endif /* LOG_UNKNOWN_HEADERS */ } } if (match(useragent_deny, hc->useragent)) { syslog(LOG_INFO, "%s matches pattern, denied!", hc->useragent); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%.80s' is denied!"), hc->encodedurl); return -1; } if (!hc->skip_redirect && httpd_redirect(hc)) return -1; if (hc->one_one) { /* Check that HTTP/1.1 requests specify a host, as required. */ if (hc->reqhost[0] == '\0' && hc->hdrhost[0] == '\0') { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "8"); return -1; } /* If the client wants to do keep-alives, it might also be doing ** pipelining. There's no way for us to tell. Since we don't ** implement keep-alives yet, if we close such a connection there ** might be unread pipelined requests waiting. So, we have to ** do a lingering close. */ if (hc->keep_alive) hc->should_linger = 1; } /* Look for a gzip accept-encoding */ if (hc->accepte[0] != '\0') { char *gz; gz = strstr(hc->accepte, "gzip"); if (gz) { char *c, *q; float qval = 0.0f; c = strstr(gz, ","); q = strstr(gz, "q="); if (q) qval = strtof(q + 2, 0); if (!q || c < q || ((!c || q < c) && qval > 0.0f)) hc->compression_type = COMPRESSION_GZIP; } } /* ** Disable keep alive support for bad browsers, ** list taken from Apache 1.3.19 */ if (hc->do_keep_alive && (strstr(hc->useragent, "Mozilla/2") || strstr(hc->useragent, "MSIE 4.0b2;"))) hc->do_keep_alive = 0; /* Ok, the request has been parsed. Now we resolve stuff that ** may require the entire request. */ /* Copy original filename to expanded filename. */ httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(hc->origfilename) + 1); strlcpy(hc->expnfilename, hc->origfilename, hc->maxexpnfilename); /* Tilde mapping. */ if (hc->expnfilename[0] == '~') { #ifdef TILDE_MAP_1 if (!tilde_map_1(hc)) { httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); return -1; } #endif #ifdef TILDE_MAP_2 if (!tilde_map_2(hc)) { httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); return -1; } #endif } /* Virtual host mapping. */ if (!hc->skip_redirect && hc->hs->vhost) { if (!vhost_map(hc)) { /* If we get here vhost_map() has logged the error */ httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); return -1; } } /* Expand all symbolic links in the filename. This also gives us ** any trailing non-existing components, for pathinfo. */ cp = expand_symlinks(hc->expnfilename, &pi, hc->hs->no_symlink_check, hc->tildemapped); if (!cp) { /* If we get here expand_symlinks() has logged the error */ httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); return -1; } /* Fall back to shared (restricted) top-level directory for missing files */ if (!hc->skip_redirect && hc->hs->vhost && is_vhost_shared(pi)) { httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(pi) + 1); strlcpy(hc->expnfilename, pi, hc->maxexpnfilename); httpd_realloc_str(&hc->pathinfo, &hc->maxpathinfo, 1); strlcpy(hc->pathinfo, "", hc->maxpathinfo); } else { httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(cp) + 1); strlcpy(hc->expnfilename, cp, hc->maxexpnfilename); httpd_realloc_str(&hc->pathinfo, &hc->maxpathinfo, strlen(pi) + 1); strlcpy(hc->pathinfo, pi, hc->maxpathinfo); } /* Remove pathinfo stuff from the original filename too. */ if (hc->pathinfo[0] != '\0') { int i; i = strlen(hc->origfilename) - strlen(hc->pathinfo); if (strcmp(&hc->origfilename[i], hc->pathinfo) == 0) { if (i == 0) hc->origfilename[0] = '\0'; else hc->origfilename[i - 1] = '\0'; } } /* If the expanded filename is an absolute path, check that it's still ** within the current directory or the alternate directory. */ if (hc->expnfilename[0] == '/') { if (strncmp(hc->expnfilename, hc->hs->cwd, strlen(hc->hs->cwd)) == 0) { /* Elide the current directory. */ memmove(hc->expnfilename, &hc->expnfilename[strlen(hc->hs->cwd)], strlen(hc->expnfilename) - strlen(hc->hs->cwd) + 1); } #ifdef TILDE_MAP_2 else if (hc->altdir[0] != '\0' && (strncmp(hc->expnfilename, hc->altdir, strlen(hc->altdir)) == 0 && (hc->expnfilename[strlen(hc->altdir)] == '\0' || hc->expnfilename[strlen(hc->altdir)] == '/'))) { } #endif else if (hc->hs->no_symlink_check) { httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); return -1; } else { syslog(LOG_NOTICE, "%.80s URL \"%s\" goes outside the web tree", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' resolves to a file outside the permitted web server directory tree.\n"), hc->encodedurl); return -1; } } return 0; } static char *bufgets(struct http_conn *hc) { int i; char c; for (i = hc->checked_idx; hc->checked_idx < hc->read_idx; ++hc->checked_idx) { c = hc->read_buf[hc->checked_idx]; if (c == '\n' || c == '\r') { hc->read_buf[hc->checked_idx] = '\0'; ++hc->checked_idx; if (c == '\r' && hc->checked_idx < hc->read_idx && hc->read_buf[hc->checked_idx] == '\n') { hc->read_buf[hc->checked_idx] = '\0'; ++hc->checked_idx; } return &(hc->read_buf[i]); } } return NULL; } static void de_dotdot(char *file) { char *cp; char *cp2; int l; /* Collapse any multiple / sequences. */ while ((cp = strstr(file, "//"))) { for (cp2 = cp + 2; *cp2 == '/'; ++cp2) continue; memmove(cp + 1, cp2, strlen(cp2) + 1); } /* Collapse leading // (first one is lost prior to this fn) */ if (file[0] == '/') memmove(file, &file[1], strlen(file)); /* Remove leading ./ and any /./ sequences. */ while (strncmp(file, "./", 2) == 0) memmove(file, file + 2, strlen(file) - 1); while ((cp = strstr(file, "/./"))) memmove(cp, cp + 2, strlen(cp) - 1); /* Alternate between removing leading ../ and removing xxx/../ */ for (;;) { while (strncmp(file, "../", 3) == 0) memmove(file, file + 3, strlen(file) - 2); cp = strstr(file, "/../"); if (!cp) break; for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) continue; memmove(cp2 + 1, cp + 4, strlen(cp + 3)); } /* Also elide any xxx/.. at the end. */ while ((l = strlen(file)) > 3 && strcmp((cp = file + l - 3), "/..") == 0) { for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) continue; if (cp2 < file) break; *cp2 = '\0'; } } struct mime_entry { char *ext; size_t ext_len; char *val; size_t val_len; }; static struct mime_entry enc_tab[] = { #include "mime_encodings.h" }; static const int n_enc_tab = sizeof(enc_tab) / sizeof(*enc_tab); static struct mime_entry typ_tab[] = { #include "mime_types.h" }; static const int n_typ_tab = sizeof(typ_tab) / sizeof(*typ_tab); /* qsort comparison routine - declared old-style on purpose, for portability. */ static int ext_compare(a, b) struct mime_entry *a; struct mime_entry *b; { return strcmp(a->ext, b->ext); } static int mime_bsearch(struct http_conn *hc, char *ext, size_t ext_len) { int top, bot, mid; int r; top = n_typ_tab - 1; bot = 0; while (top >= bot) { mid = (top + bot) / 2; r = strncasecmp(ext, typ_tab[mid].ext, ext_len); if (r < 0) top = mid - 1; else if (r > 0) bot = mid + 1; else if (ext_len < typ_tab[mid].ext_len) top = mid - 1; else if (ext_len > typ_tab[mid].ext_len) bot = mid + 1; else { hc->type = typ_tab[mid].val; return 1; /* found */ } } return 0; } static void init_mime(void) { int i; /* Sort the tables so we can do binary search. */ qsort(enc_tab, n_enc_tab, sizeof(*enc_tab), ext_compare); qsort(typ_tab, n_typ_tab, sizeof(*typ_tab), ext_compare); /* Fill in the lengths. */ for (i = 0; i < n_enc_tab; ++i) { enc_tab[i].ext_len = strlen(enc_tab[i].ext); enc_tab[i].val_len = strlen(enc_tab[i].val); } for (i = 0; i < n_typ_tab; ++i) { typ_tab[i].ext_len = strlen(typ_tab[i].ext); typ_tab[i].val_len = strlen(typ_tab[i].val); } } /* Figure out MIME encodings and type based on the filename. Multiple ** encodings are separated by commas, and are listed in the order in ** which they were applied to the file. */ static void figure_mime(struct http_conn *hc) { const char *default_type = "text/plain; charset=%s"; size_t ext_len, n_me_indexes; char *prev_dot; char *dot; char *ext; int me_indexes[100]; int i; /* Peel off encoding extensions until there aren't any more. */ n_me_indexes = 0; hc->type = default_type; for (prev_dot = &hc->expnfilename[strlen(hc->expnfilename)];; prev_dot = dot) { int candidate = 0; for (dot = prev_dot - 1; dot >= hc->expnfilename && *dot != '.'; --dot) ; /* No dot found. No more extensions. */ if (dot < hc->expnfilename) break; ext = dot + 1; ext_len = prev_dot - ext; /* Search encodings table. Linear search is fine here, ** there are only a few entries. */ for (i = 0; i < n_enc_tab; ++i) { if (ext_len == enc_tab[i].ext_len && strncasecmp(ext, enc_tab[i].ext, ext_len) == 0) { if (n_me_indexes < NELEMS(me_indexes)) { me_indexes[n_me_indexes++] = i; } if (mime_bsearch(hc, ext, ext_len)) candidate = 1; break; } } /* We have a candidate for Content-Type, no go see if we ** can do better. I.e., if encodings mechanism found a ** .gz we have application/gzip, but the actual file may ** be a tar.gz that we want to have application/x-tar. ** ** If it turns out the file is something like .html.gz ** we fall back to the candidate Content-Type. */ if (candidate) continue; /* Binary search for a matching type extension. */ if (mime_bsearch(hc, ext, ext_len)) break; } /* The last thing we do is actually generate the mime-encoding header. */ hc->encodings[0] = '\0'; for (i = n_me_indexes - 1; i >= 0; --i) { size_t len; len = strlen(hc->encodings) + enc_tab[me_indexes[i]].val_len + 2; httpd_realloc_str(&hc->encodings, &hc->maxencodings, len); if (hc->encodings[0] != '\0') strlcat(hc->encodings, ",", hc->maxencodings); strlcat(hc->encodings, enc_tab[me_indexes[i]].val, hc->maxencodings); } } #ifdef CGI_TIMELIMIT static void cgi_kill2(arg_t arg, struct timeval *now) { pid_t pid; pid = (pid_t)arg.i; if (kill(pid, SIGKILL) == 0) syslog(LOG_ERR, "hard-killed CGI process %d", pid); } static void cgi_kill(arg_t arg, struct timeval *now) { pid_t pid; pid = (pid_t)arg.i; if (kill(pid, SIGINT) == 0) { syslog(LOG_ERR, "killed CGI process %d", pid); /* In case this isn't enough, schedule an uncatchable kill. */ if (!tmr_create(now, cgi_kill2, arg, 5 * 1000L, 0)) { syslog(LOG_CRIT, "tmr_create(cgi_kill2) failed"); exit(1); } } } #endif /* CGI_TIMELIMIT */ #ifdef GENERATE_INDEXES /* Convert byte size to kiB, MiB, GiB */ static char *humane_size(struct stat *st) { size_t i = 0; off_t bytes; char *mult[] = { "", "k", "M", "G", "T", "P" }; static char str[42]; if (S_ISDIR(st->st_mode)) { snprintf(str, sizeof(str), " - "); return str; } bytes = st->st_size; while (bytes > 1000 && i < NELEMS(mult)) { bytes /= 1000; i++; } snprintf(str, sizeof(str), " %ld%s", (long int)bytes, mult[i]); return str; } static int is_reserved_htfile(const char *fn) { int i; const char *res[] = { #ifdef AUTH_FILE AUTH_FILE, #else ".htpasswd", #endif #ifdef ACCESS_FILE ACCESS_FILE, #else ".htaccess", #endif NULL }; for (i = 0; res[i]; i++) { if (!strcmp(res[i], fn)) return 1; } return 0; } /* qsort comparison routine - declared old-style on purpose, for portability. */ static int name_compare(a, b) char **a; char **b; { return strcmp(*a, *b); } static int child_ls_read_names(struct http_conn *hc, DIR *dirp, FILE *fp, int onlydir) { int i, nnames = 0; static int maxnames = 0; struct dirent *de; static char *names; static char **nameptrs; static char *name; static size_t maxname = 0; static char *rname; static size_t maxrname = 0; static char *encrname; static size_t maxencrname = 0; while ((de = readdir(dirp))) { char *path; if (!strcmp(".", de->d_name)) continue; if (!strcmp("..", de->d_name)) continue; path = realpath(de->d_name, NULL); if (!path) { struct stat st; httpd_realloc_str(&name, &maxname, strlen(hc->expnfilename) + 1 + strlen(de->d_name)); snprintf(name, maxname, "%s/%s", hc->expnfilename, de->d_name); if (stat(name, &st)) continue; if (!(st.st_mode & (S_IROTH | S_IXOTH))) continue; fallback: if (onlydir && de->d_type != DT_DIR) continue; if (!onlydir && de->d_type == DT_DIR) continue; } else { struct stat st; if (stat(path, &st)) { free(path); goto fallback; } if (!(st.st_mode & (S_IROTH | S_IXOTH))) continue; free(path); if (onlydir && !S_ISDIR(st.st_mode)) continue; if (!onlydir && S_ISDIR(st.st_mode)) continue; } if (nnames >= maxnames) { if (maxnames == 0) { maxnames = 100; names = NEW(char, maxnames * (MAXPATHLEN + 1)); nameptrs = NEW(char *, maxnames); } else { maxnames *= 2; names = RENEW(names, char, maxnames * (MAXPATHLEN + 1)); nameptrs = RENEW(nameptrs, char *, maxnames); } if (!names || !nameptrs) { syslog(LOG_ERR, "out of memory reallocating directory names"); return 1; } for (i = 0; i < maxnames; ++i) nameptrs[i] = &names[i * (MAXPATHLEN + 1)]; } if (nameptrs && nnames < maxnames) strlcpy(nameptrs[nnames++], de->d_name, MAXPATHLEN + 1); } /* Sort the names. */ if (nameptrs) qsort(nameptrs, nnames, sizeof(*nameptrs), name_compare); /* Generate output. */ for (i = 0; i < nnames; ++i) { struct stat sb; struct stat lsb; char buf[256]; char timestr[42]; char *icon, *alt; if (!strcmp(nameptrs[i], ".")) continue; if (!strcmp(nameptrs[i], "..")) { if (!strcmp(hc->encodedurl, "/")) continue; fprintf(fp, " \n" " \"↩\"\n" " Parent Directory\n" "  \n" "  \n" " \n"); continue; } /* Skip listing dotfiles unless enabled in .conf file */ if (!hc->hs->list_dotfiles && nameptrs[i][0] == '.' && strlen(nameptrs[i]) > 2) continue; /* Do not show .htpasswd and .htaccess files */ if (is_reserved_htfile(nameptrs[i])) continue; httpd_realloc_str(&name, &maxname, strlen(hc->expnfilename) + 2 + strlen(nameptrs[i])); httpd_realloc_str(&rname, &maxrname, strlen(hc->origfilename) + 2 + strlen(nameptrs[i])); if (hc->expnfilename[0] == '\0' || strcmp(hc->expnfilename, ".") == 0) { strlcpy(name, nameptrs[i], maxname); strlcpy(rname, nameptrs[i], maxrname); } else { snprintf(name, maxname, "%s/%s", hc->expnfilename, nameptrs[i]); if (strcmp(hc->origfilename, ".") == 0) snprintf(rname, maxrname, "%s", nameptrs[i]); else snprintf(rname, maxrname, "%s%s", hc->origfilename, nameptrs[i]); } httpd_realloc_str(&encrname, &maxencrname, 3 * strlen(rname) + 1); strencode(encrname, maxencrname, rname); if (stat(name, &sb) < 0 || lstat(name, &lsb) < 0) continue; /* Get time string. */ strftime(timestr, sizeof(timestr), "%F  %R", localtime(&lsb.st_mtime)); /* The ls -F file class. */ switch (sb.st_mode & S_IFMT) { case S_IFDIR: icon = "/icons/folder.gif"; alt = "📁"; break; default: icon = "/icons/generic.gif"; alt = "📄"; break; } defang(nameptrs[i], buf, sizeof(buf)); fprintf(fp, " \n" " \"%s\"\n" " %s\n" " %s\n" " %s\n" " \n", icon, alt, encrname, S_ISDIR(sb.st_mode) ? "/" : "", buf, humane_size(&lsb), timestr); } return 0; } /* Forked child process from ls() */ static int child_ls(struct http_conn *hc, DIR *dirp) { FILE *fp; long len; char *proto; char *buf; fp = tempfile(); if (!fp) { syslog(LOG_ERR, "tmpfile: %s", strerror(errno)); error: httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_send_response(hc); return 1; } if (hc->ssl) proto = "https"; else proto = "http"; fprintf(fp, "\n" "\n" " \n" " Index of %s://%s%s\n" " \n" " \n" "%s" " \n" " \n" "
\n" "

Index of %s://%s%s

\n" "\n" "
" "\n" " " " \n" " \n" " \n" " \n" " \n", proto, get_hostname(hc), hc->encodedurl, httpd_css_default(), proto, get_hostname(hc), hc->encodedurl); /* Read in names. */ child_ls_read_names(hc, dirp, fp, 1); rewinddir(dirp); child_ls_read_names(hc, dirp, fp, 0); fprintf(fp, "
\" \"NameSizeLast modified
\n"); fprintf(fp, "
%s httpd at %s port %d
\n", EXPOSED_SERVER_SOFTWARE, get_hostname(hc), (int)hc->hs->port); fprintf(fp, "
\n\n"); len = ftell(fp); if (len == -1) { syslog(LOG_ERR, "ftell: %s", strerror(errno)); (void)fclose(fp); goto error; } buf = malloc((size_t)len); if (!buf) { syslog(LOG_ERR, "Failed allocating %ld bytes in child_ls(): %s", len, strerror(errno)); (void)fclose(fp); goto error; } send_mime(hc, 200, ok200title, "", "", "text/html; charset=%s", (off_t) - 1, hc->sb.st_mtime); httpd_send_response(hc); rewind(fp); fread(buf, (size_t)len, 1, fp); if (httpd_write(hc, buf, (size_t)len) <= 0) { if (hc->errmsg) syslog(LOG_ERR, "Failed sending dirlisting to client: %s", hc->errmsg); } free(buf); (void)fclose(fp); return 0; } static int ls(struct http_conn *hc) { DIR *dirp; hc->compression_type = COMPRESSION_NONE; dirp = opendir(hc->expnfilename); if (!dirp) { syslog(LOG_ERR, "opendir %s: %s", hc->expnfilename, strerror(errno)); httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); return -1; } if (hc->method == METHOD_HEAD) { closedir(dirp); send_mime(hc, 200, ok200title, "", "", "text/html; charset=%s", (off_t) - 1, hc->sb.st_mtime); } else if (hc->method == METHOD_GET) { child_ls(hc, dirp); closedir(dirp); syslog(LOG_INFO, "%.80s: LST /%.200s \"%s\" \"%s\"", httpd_client(hc), hc->expnfilename, hc->referer, hc->useragent); hc->status = 200; hc->bytes_sent = CGI_BYTECOUNT; hc->should_linger = 0; } else { closedir(dirp); httpd_send_err(hc, 501, err501title, "", err501form, httpd_method_str(hc->method)); return -1; } return 0; } #endif /* GENERATE_INDEXES */ static int is_php(struct http_conn *hc, char *fn) { assert(hc); assert(hc->hs); if (!fn) fn = hc->expnfilename; if (hc->hs->php_pattern && match(hc->hs->php_pattern, fn)) return 1; return 0; } static int is_ssi(struct http_conn *hc, char *fn) { assert(hc); assert(hc->hs); if (!fn) fn = hc->expnfilename; if (hc->hs->ssi_pattern && match(hc->hs->ssi_pattern, fn)) return 1; return 0; } static char *build_env(char *fmt, char *arg) { char *cp; size_t size; static char *buf; static size_t maxbuf = 0; size = strlen(fmt) + strlen(arg); if (size > maxbuf) httpd_realloc_str(&buf, &maxbuf, size); snprintf(buf, maxbuf, fmt, arg); cp = strdup(buf); if (!cp) { syslog(LOG_ERR, "out of memory copying environment variable"); exit(1); } return cp; } #ifdef SERVER_NAME_LIST static char *hostname_map(char *hostname) { int len, n; static char *list[] = { SERVER_NAME_LIST }; len = strlen(hostname); for (n = sizeof(list) / sizeof(*list) - 1; n >= 0; --n) { if (strncasecmp(hostname, list[n], len) == 0) { if (list[n][len] == '/') /* check in case of a substring match */ return &list[n][len + 1]; } } return NULL; } #endif /* SERVER_NAME_LIST */ /* Set up environment variables. Be real careful here to avoid ** letting malicious clients overrun a buffer. We don't have ** to worry about freeing stuff since we're a sub-process. */ static char **make_envp(struct http_conn *hc) { static char *envp[50]; int envn; char *cp; char buf[256]; envn = 0; envp[envn++] = build_env("PATH=%s", CGI_PATH); #ifdef CGI_LD_LIBRARY_PATH envp[envn++] = build_env("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH); #endif envp[envn++] = build_env("SERVER_SOFTWARE=%s", SERVER_SOFTWARE); /* If vhosting, use that server-name here. */ cp = get_hostname(hc); if (cp[0]) envp[envn++] = build_env("SERVER_NAME=%s", cp); envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1"; envp[envn++] = build_env("SERVER_PROTOCOL=%s", hc->protocol); snprintf(buf, sizeof(buf), "%d", (int)hc->hs->port); envp[envn++] = build_env("SERVER_PORT=%s", buf); envp[envn++] = build_env("REQUEST_METHOD=%s", httpd_method_str(hc->method)); envp[envn++] = build_env("REQUEST_URI=%s", hc->encodedurl); if (hc->pathinfo[0] != '\0') { char *cp2; size_t l; envp[envn++] = build_env("PATH_INFO=/%s", hc->pathinfo); l = strlen(hc->hs->cwd) + strlen(hc->pathinfo) + 1; cp2 = NEW(char, l); if (cp2) { snprintf(cp2, l, "%s%s", hc->hs->cwd, hc->pathinfo); envp[envn++] = build_env("PATH_TRANSLATED=%s", cp2); } } else if (is_ssi(hc, NULL)) { char buf[4]; char *cp2; size_t l; snprintf(buf, sizeof(buf), "%d", loglevel); envp[envn++] = build_env("IDENT=%s", ident); envp[envn++] = build_env("LOG_LEVEL=%s", buf); /* Fake PATH_INFO and PATH_TRANSLATED for cgi-bin/ssi ** since it uses them to determinte include paths for ** virtual/config SSI directives. */ envp[envn++] = build_env("PATH_INFO=/%s", hc->expnfilename); l = strlen(hc->hs->cwd) + strlen(hc->expnfilename) + 1; cp2 = NEW(char, l); if (cp2) { snprintf(cp2, l, "%s%s", hc->hs->cwd, hc->expnfilename); envp[envn++] = build_env("PATH_TRANSLATED=%s", cp2); } if (ssi_silent) envp[envn++] = build_env("SILENT_ERRORS=%s", "true"); } envp[envn++] = build_env("SCRIPT_NAME=/%s", strcmp(hc->origfilename, ".") == 0 ? "" : hc->origfilename); /* php-cgi needs non-std SCRIPT_FILENAME to be defined to detect ** it was invoked as CGI script. */ if (is_php(hc, NULL)) { char *dedot; if (strcmp(hc->expnfilename, ".") == 0) dedot = ""; else dedot = hc->expnfilename; if (hc->expnfilename[0] == '/') snprintf(buf, sizeof(buf), "%s", dedot); else snprintf(buf, sizeof(buf), "%s%s", hc->hs->cwd, dedot); envp[envn++] = build_env("SCRIPT_FILENAME=%s", buf); /* See more about this at https://php.net/security.cgi-bin */ envp[envn++] = build_env("REDIRECT_STATUS=%s", "1"); } if (hc->query[0] != '\0') envp[envn++] = build_env("QUERY_STRING=%s", hc->query); envp[envn++] = build_env("REMOTE_ADDR=%s", httpd_client(hc)); /* RFC 3875, section 4.1.9, states the server *should* set this, ** but that it *may* susbstitute it with the IP address for ** performance reasons. */ envp[envn++] = build_env("REMOTE_HOST=%s", httpd_client(hc)); /* Non-standard, but common, client's port */ snprintf(buf, sizeof(buf), "%hd", httpd_client_port(hc)); envp[envn++] = build_env("REMOTE_PORT=%s", buf); if (hc->referer[0] != '\0') envp[envn++] = build_env("HTTP_REFERER=%s", hc->referer); if (hc->useragent[0] != '\0') envp[envn++] = build_env("HTTP_USER_AGENT=%s", hc->useragent); if (hc->accept[0] != '\0') envp[envn++] = build_env("HTTP_ACCEPT=%s", hc->accept); if (hc->accepte[0] != '\0') envp[envn++] = build_env("HTTP_ACCEPT_ENCODING=%s", hc->accepte); if (hc->acceptl[0] != '\0') envp[envn++] = build_env("HTTP_ACCEPT_LANGUAGE=%s", hc->acceptl); if (hc->cookie[0] != '\0') envp[envn++] = build_env("HTTP_COOKIE=%s", hc->cookie); if (hc->contenttype[0] != '\0') envp[envn++] = build_env("CONTENT_TYPE=%s", hc->contenttype); if (hc->hdrhost[0] != '\0') envp[envn++] = build_env("HTTP_HOST=%s", hc->hdrhost); if (hc->contentlength > 0) { snprintf(buf, sizeof(buf), "%lu", (unsigned long)hc->contentlength); envp[envn++] = build_env("CONTENT_LENGTH=%s", buf); } if (hc->remoteuser[0] != '\0') envp[envn++] = build_env("REMOTE_USER=%s", hc->remoteuser); if (hc->authorization[0] != '\0') { /* We only support Basic auth at the moment. */ envp[envn++] = build_env("AUTH_TYPE=%s", "Basic"); /* known workaround for Apache & php5-cgi, now it works in Merecat too */ envp[envn++] = build_env("HTTP_AUTHORIZATION=%s", hc->authorization); } if (getenv("TZ")) envp[envn++] = build_env("TZ=%s", getenv("TZ")); envp[envn++] = build_env("CGI_PATTERN=%s", hc->hs->cgi_pattern); envp[envn] = NULL; return envp; } /* Set up argument vector. Again, we don't have to worry about freeing stuff ** since we're a sub-process. This gets done after make_envp() because we ** scribble on hc->query. */ static char **make_argp(struct http_conn *hc) { char **argp; char *cgi = NULL; char *cp1; char *cp2; int argn = 0; int num = 2; if (is_php(hc, NULL)) { cgi = hc->hs->php_cgi; num++; } if (is_ssi(hc, NULL)) { cgi = hc->hs->ssi_cgi; num++; } /* By allocating an arg slot for every character in the query, plus ** one for the filename and one for the NULL, we are guaranteed to ** have enough. We could actually use strlen/2. */ argp = NEW(char *, strlen(hc->query) + num); if (!argp) return NULL; if (cgi) { cp1 = strrchr(cgi, '/'); if (cp1) argp[argn++] = ++cp1; else argp[argn++] = cgi; } argp[argn] = strrchr(hc->expnfilename, '/'); if (argp[argn]) ++argp[argn++]; else argp[argn++] = hc->expnfilename; /* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html, ** "The server should search the query information for a non-encoded = ** character to determine if the command line is to be used, if it finds ** one, the command line is not to be used." */ if (!strchr(hc->query, '=')) { for (cp1 = cp2 = hc->query; *cp2 != '\0'; ++cp2) { if (*cp2 == '+') { *cp2 = '\0'; strdecode(cp1, cp1); argp[argn++] = cp1; cp1 = cp2 + 1; } } if (cp2 != cp1) { strdecode(cp1, cp1); argp[argn++] = cp1; } } argp[argn] = NULL; return argp; } /* This routine is used only for POST requests. It reads the data ** from the request and sends it to the child process. The only reason ** we need to do it this way instead of just letting the child read ** directly is that we have already read part of the data into our ** buffer. */ static void cgi_interpose_input(struct http_conn *hc, int wfd) { size_t c; ssize_t r; char buf[1024]; c = hc->read_idx - hc->checked_idx; if (c > 0) { r = file_write(wfd, &(hc->read_buf[hc->checked_idx]), c); if ((size_t)r != c) return; } while (c < hc->contentlength) { r = httpd_read(hc, buf, MIN(sizeof(buf), hc->contentlength - c)); if (r < 0 && (errno == EINTR || errno == EAGAIN)) { sleep(1); continue; } if (r <= 0) return; if (file_write(wfd, buf, r) != r) return; c += r; } post_post_garbage_hack(hc); } /* Special hack to deal with broken browsers that send a LF or CRLF ** after POST data, causing TCP resets - we just read and discard up ** to 2 bytes. Unfortunately this doesn't fix the problem for CGIs ** which avoid the interposer process due to their POST data being ** short. Creating an interposer process for all POST CGIs is ** unacceptably expensive. The eventual fix will come when interposing ** gets integrated into the main loop as a tasklet instead of a process. */ static void post_post_garbage_hack(struct http_conn *hc) { char buf[2]; /* If we are in a sub-process, turn on no-delay mode in case we ** previously cleared it. */ if (sub_process) (void)httpd_set_ndelay(hc->conn_fd); /* And read up to 2 bytes. */ httpd_read(hc, buf, sizeof(buf)); } /* Normalize newlines from CGI to RFC3875 \r\n format, for details see ** https://datatracker.ietf.org/doc/html/rfc3875#section-6.3.4 */ static void cgi_normalize_newline(char **headers, size_t *headers_len, size_t *headers_size, size_t len) { size_t i, j, buf_size; int lf_count, was_cr; char *buf; /* If newline separating headers and body is "\n\n" correct it to. */ if (strncmp(&((*headers)[len]), "\n\n", 2) == 0) len += 2; /* Count \n without preceding \r in headers section */ was_cr = 0; lf_count = 0; for (i = 0; i < len; i++) { switch ((*headers)[i]) { case '\r': was_cr = 1; break; case '\n': if (!was_cr) lf_count++; /* fallthrough */ default: was_cr = 0; break; } } if (lf_count == 0) return; /* Allocate memory accounting for newly inserted '\r'. */ buf_size = 0; httpd_realloc_str(&buf, &buf_size, *headers_len + lf_count); /* Copy headers and normalize all line endings as \r\n */ was_cr = 0; for (i = 0, j = 0; i < len; i++, j++) { switch ((*headers)[i]) { case '\r': buf[j] = (*headers)[i]; was_cr = 1; break; case '\n': if (!was_cr) buf[j++] = '\r'; /* fallthrough */ default: buf[j] = (*headers)[i]; was_cr = 0; break; } } /* Copy rest of buffer after http headers. */ memcpy(&(buf[j]), &((*headers)[i]), *headers_len - len); /* Replace old buffer with processed one and update sizes. */ free(*headers); *headers_len += lf_count; *headers_size = buf_size; *headers = buf; } /* This routine is used for parsed-header CGIs. The idea here is that the ** CGI can return special headers such as "Status:" and "Location:" which ** change the return status of the response. Since the return status has to ** be the very first line written out, we have to accumulate all the headers ** and check for the special ones before writing the status. Then we write ** out the saved headers and proceed to echo the rest of the response. */ static void cgi_interpose_output(struct http_conn *hc, int rfd) { ssize_t r; char buf[1024]; size_t headers_size, headers_len; char *headers; char *br; int status; char *title; char *cp; /* Make sure the connection is in blocking mode. It should already ** be blocking, but we might as well be sure. */ (void)httpd_clear_ndelay(hc->conn_fd); /* Slurp in all headers. */ headers_size = 0; httpd_realloc_str(&headers, &headers_size, 500); headers_len = 0; for (;;) { r = file_read(rfd, buf, sizeof(buf)); if (r <= 0) { br = &(headers[headers_len]); break; } httpd_realloc_str(&headers, &headers_size, headers_len + r); memmove(&(headers[headers_len]), buf, r); headers_len += r; headers[headers_len] = '\0'; if ((br = strstr(headers, "\r\n\r\n")) || (br = strstr(headers, "\n\n"))) break; } /* If there were no headers, bail. */ if (headers[0] == '\0') { free(headers); return; } /* Figure out the status. Look for a Status: or Location: header; ** else if there's an HTTP header line, get it from there; else ** default to 200. */ status = 200; if (strncmp(headers, "HTTP/", 5) == 0) { cp = headers; cp += strcspn(cp, " \t"); status = atoi(cp); } if ((cp = strstr(headers, "Status:")) && cp < br && (cp == headers || *(cp - 1) == '\n')) { cp += 7; cp += strspn(cp, " \t"); status = atoi(cp); } else if ((cp = strstr(headers, "Location:")) && cp < br && (cp == headers || *(cp - 1) == '\n')) { status = 302; } /* Ensure newlines are RFC style \r\n, not just UNIX \n */ cgi_normalize_newline(&headers, &headers_len, &headers_size, br - headers); /* Write the status line. */ switch (status) { case 200: title = ok200title; break; case 302: title = err302title; break; case 304: title = err304title; break; case 400: title = httpd_err400title; break; #ifdef AUTH_FILE case 401: title = err401title; break; #endif case 403: title = err403title; break; case 404: title = err404title; break; case 408: title = httpd_err408title; break; case 500: title = err500title; break; case 501: title = err501title; break; case 503: title = httpd_err503title; break; default: title = "Something"; break; } snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\r\n", status, title); httpd_write(hc, buf, strlen(buf)); /* Write the saved headers. */ httpd_write(hc, headers, headers_len); /* Echo the rest of the output. */ for (;;) { r = file_read(rfd, buf, sizeof(buf)); if (r <= 0) break; if (httpd_write(hc, buf, r) != r) break; } free(headers); shutdown(hc->conn_fd, SHUT_WR); } /* CGI child process. */ static void cgi_child(struct http_conn *hc) { int r; char **argp; char **envp; char *binary; char *directory; /* Unset close-on-exec flag for this socket. This actually shouldn't ** be necessary, according to POSIX a dup()'d file descriptor does ** *not* inherit the close-on-exec flag, its flag is always clear. ** However, Linux messes this up and does copy the flag to the ** dup()'d descriptor, so we have to clear it. This could be ** ifdeffed for Linux only. */ (void)clear_cloexec(hc->conn_fd); /* If the connection happens to be using one of the stdio ** descriptors move it to another descriptor so that the ** dup2() calls below don't screw things up. */ if (hc->conn_fd == STDIN_FILENO || hc->conn_fd == STDOUT_FILENO || hc->conn_fd == STDERR_FILENO) { int newfd = dup(hc->conn_fd); if (newfd >= 0) hc->conn_fd = newfd; /* If the dup() fails, shrug. We'll just take our ** chances. Shouldn't happen though. */ } /* Make the environment vector. */ envp = make_envp(hc); /* Make the argument vector. */ argp = make_argp(hc); /* Set up stdin. For POSTs we may have to set up a pipe from an ** interposer process, depending on if we've read some of the data ** into our buffer. */ if ((hc->method == METHOD_POST || hc->method == METHOD_PUT) && hc->read_idx >= hc->checked_idx) { int p[2]; if (pipe(p) < 0) { syslog(LOG_ERR, "pipe: %s", strerror(errno)); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_send_response(hc); exit(1); } r = fork(); if (r < 0) { syslog(LOG_ERR, "fork: %s", strerror(errno)); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_send_response(hc); exit(1); } if (r == 0) { /* Interposer process. */ sub_process = 1; (void)close(p[0]); cgi_interpose_input(hc, p[1]); exit(0); } /* Need to schedule a kill for process r; but in the main process! */ (void)close(p[1]); if (p[0] != STDIN_FILENO) { dup2(p[0], STDIN_FILENO); (void)close(p[0]); } } else { /* Otherwise, the request socket is stdin. */ if (hc->conn_fd != STDIN_FILENO) dup2(hc->conn_fd, STDIN_FILENO); } /* Set up stdout/stderr. If we're doing CGI header parsing, ** we need an output interposer too. */ if (strncmp(argp[0], "nph-", 4) != 0 && hc->mime_flag) { int p[2]; if (pipe(p) < 0) { syslog(LOG_ERR, "pipe: %s", strerror(errno)); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_send_response(hc); exit(1); } r = fork(); if (r < 0) { syslog(LOG_ERR, "fork: %s", strerror(errno)); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_send_response(hc); exit(1); } if (r == 0) { /* Interposer process. */ sub_process = 1; (void)close(p[1]); cgi_interpose_output(hc, p[0]); exit(0); } /* Need to schedule a kill for process r; but in the main process! */ (void)close(p[0]); if (p[1] != STDOUT_FILENO) dup2(p[1], STDOUT_FILENO); if (p[1] != STDERR_FILENO) dup2(p[1], STDERR_FILENO); if (p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO) (void)close(p[1]); } else { /* Otherwise, the request socket is stdout/stderr. */ if (hc->conn_fd != STDOUT_FILENO) dup2(hc->conn_fd, STDOUT_FILENO); if (hc->conn_fd != STDERR_FILENO) dup2(hc->conn_fd, STDERR_FILENO); } /* At this point we would like to set close-on-exec again for hc->conn_fd ** (see previous comments on Linux's broken behavior re: close-on-exec ** and dup.) Unfortunately there seems to be another Linux problem, or ** perhaps a different aspect of the same problem - if we do this ** close-on-exec in Linux, the socket stays open but stderr gets ** closed - the last fd duped from the socket. What a mess. So we'll ** just leave the socket as is, which under other OSs means an extra ** file descriptor gets passed to the child process. Since the child ** probably already has that file open via stdin stdout and/or stderr, ** this is not a problem. */ /* (void)set_cloexec(hc->conn_fd); */ #ifdef CGI_NICE /* Set priority. */ nice(CGI_NICE); #endif /* argp[0] is already set up with the basename of php_cgi */ if (is_php(hc, NULL)) binary = hc->hs->php_cgi; else if (is_ssi(hc, NULL)) binary = hc->hs->ssi_cgi; else { /* Split the program into directory and binary, so we can chdir() ** to the program's own directory. This isn't in the CGI 1.1 ** spec, but it's what other HTTP servers do. */ directory = strdup(hc->expnfilename); if (!directory) { binary = hc->expnfilename; /* ignore errors */ } else { binary = strrchr(directory, '/'); if (!binary) { binary = hc->expnfilename; } else { char *ptr; ptr = strstr(directory, "/./"); if (ptr && (ptr + 2) == binary) binary = ptr; *binary++ = '\0'; chdir(directory); /* ignore errors */ } } } /* Default behavior for SIGPIPE. */ signal(SIGPIPE, SIG_DFL); /* Close the syslog descriptor so that the CGI program can't ** mess with it. All other open descriptors should be either ** the listen socket(s), sockets from accept(), or the file-logging ** fd, and all of those are set to close-on-exec, so we don't ** have to close anything else. */ closelog(); /* Run the program. */ execve(binary, argp, envp); /* Something went wrong, in a chroot() we may not get this syslog() msg. */ syslog(LOG_ERR, "execve %s(%s): %s", binary, hc->expnfilename, strerror(errno)); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); httpd_send_response(hc); exit(1); } int httpd_cgi_track(struct httpd *hs, pid_t pid) { int i; for (i = 0; i < hs->cgi_limit; i++) { if (hs->cgi_tracker[i] != 0) continue; hs->cgi_tracker[i] = pid; hs->cgi_count++; return 0; } return 1; /* Error, no space? */ } int httpd_cgi_untrack(struct httpd *hs, pid_t pid) { int i; for (i = 0; i < hs->cgi_limit; i++) { if (hs->cgi_tracker[i] != pid) continue; hs->cgi_tracker[i] = 0; hs->cgi_count--; return 0; } return 1; /* Not found in this server */ } static int cgi(struct http_conn *hc) { arg_t arg; int pid; /* ** We are not going to leave the socket open after a CGI ... too difficult */ hc->do_keep_alive = 0; if (hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit) { httpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl); return -1; } (void)httpd_clear_ndelay(hc->conn_fd); pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fork: %s", strerror(errno)); httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); return -1; } if (pid == 0) { /* Child process. */ sub_process = 1; httpd_unlisten(hc->hs); cgi_child(hc); } /* Parent process spawned CGI process PID. */ syslog(LOG_INFO, "%.80s: CGI[%d] /%.200s%s \"%s\" \"%s\"", httpd_client(hc), pid, hc->expnfilename, hc->encodedurl, hc->referer, hc->useragent); httpd_cgi_track(hc->hs, pid); #ifdef CGI_TIMELIMIT /* Schedule a kill for the child process, in case it runs too long */ arg.i = pid; if (!tmr_create(NULL, cgi_kill, arg, CGI_TIMELIMIT * 1000L, 0)) { syslog(LOG_CRIT, "tmr_create(cgi_kill child) failed"); exit(1); } #endif hc->status = 200; hc->bytes_sent = CGI_BYTECOUNT; hc->should_linger = 0; return 0; } /* * This function checks the requested (expanded) filename against the * CGI pattern, taking into account if the filename is prefixed with * a VHOST. */ static int is_cgi(struct http_conn *hc) { char *fn; assert(hc); assert(hc->hs); fn = hc->expnfilename; if (hc->hs->vhost) { int len; char buf[256]; len = snprintf(buf, sizeof(buf), "%s/**", hc->hostdir) - 2; if (match(buf, fn)) fn += len; } /* With the vhost prefix out of the way we can match CGI patterns */ if (hc->hs->cgi_pattern && match(hc->hs->cgi_pattern, fn)) return 1; if (is_php(hc, fn)) return 1; if (is_ssi(hc, fn)) return 1; return 0; } /* ** Adds Vary: Accept-Encoding to relevant files. For details, see ** https://www.maxcdn.com/blog/accept-encoding-its-vary-important/ ** TODO: Refactor, too much focus on finding .gz files */ static char *mod_headers(struct http_conn *hc) { int serve_dotgz = 0; char *fn = NULL; char *header = ""; char *match[] = { ".js", ".css", ".xml", ".gz", ".html" }; size_t i, len; struct stat st; if (hc->compression_type != COMPRESSION_GZIP) goto done; /* construct .gz filename, remember NUL */ len = strlen(hc->expnfilename) + 4; fn = malloc(len); snprintf(fn, len, "%s.gz", hc->expnfilename); /* is there a .gz file */ if (!stat(fn, &st)) { /* Is it world-readable or world-executable? and newer than original */ if (st.st_mode & (S_IROTH | S_IXOTH) && st.st_mtime >= hc->sb.st_mtime) serve_dotgz = 1; } /* can serve .gz file and there is no previous encodings */ if (serve_dotgz && hc->encodings[0] == 0) { httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, strlen(fn) + 2); strlcpy(hc->expnfilename, fn, hc->maxexpnfilename); hc->sb.st_size = st.st_size; hc->compression_type = COMPRESSION_NONE; /* Compressed already, do not call zlib */ httpd_realloc_str(&hc->encodings, &hc->maxencodings, 5); strncpy(hc->encodings, "gzip", hc->maxencodings); } free(fn); done: /* no zlib */ if (!hc->has_deflate) hc->compression_type = COMPRESSION_NONE; /* don't try to compress non-text files unless it's javascript */ else if (strncmp(hc->type, "text/", 5) && strcmp(hc->type, "application/javascript")) hc->compression_type = COMPRESSION_NONE; /* don't try to compress really small things */ else if (hc->sb.st_size < 256) hc->compression_type = COMPRESSION_NONE; fn = strrchr(hc->expnfilename, '.'); if (fn || strstr(hc->encodings, "gzip")) { for (i = 0; i < NELEMS(match); i++) { if (strcmp(fn, match[i])) continue; header = "Vary: Accept-Encoding\r\n"; break; } } return header; } int httpd_start_request(struct http_conn *hc, struct timeval *now) { static const char *index_names[] = { INDEX_NAMES }; size_t expnlen, indxlen, i; char *cp, *pi; int is_icon; is_icon = mmc_icon_check(hc->decodedurl, &hc->sb); if (is_icon) { strcpy(hc->expnfilename, hc->decodedurl); strcpy(hc->pathinfo, ""); goto sneaky; } /* Stat the file. */ if (stat(hc->expnfilename, &hc->sb) < 0) { if (ENOENT == errno) httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); else httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); return -1; } /* Is it world-readable or world-executable? We check explicitly instead ** of just trying to open it, so that no one ever gets surprised by ** a file that's not set world-readable and yet somehow is ** readable by the HTTP server and therefore the *whole* world. */ if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH))) { syslog(LOG_INFO, "%.80s URL \"%s\" resolves to a non world-readable file", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' resolves to a file that is not world-readable.\n"), hc->encodedurl); return -1; } expnlen = strlen(hc->expnfilename); /* Is it a directory? */ if (S_ISDIR(hc->sb.st_mode)) { /* If there's pathinfo, it's just a non-existent file. */ if (hc->pathinfo[0] != '\0') { httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); return -1; } /* Special handling for directory URLs that don't end in a slash. ** We send back an explicit redirect with the slash, because ** otherwise many clients can't build relative URLs properly. */ if (strcmp(hc->origfilename, "") != 0 && strcmp(hc->origfilename, ".") != 0 && hc->origfilename[strlen(hc->origfilename) - 1] != '/') { send_dirredirect(hc); return -1; } /* Check for an index file. */ for (i = 0; i < NELEMS(index_names); ++i) { /* +2 = extra slash plus \0, strlen() returns length without \0 */ httpd_realloc_str(&hc->indexname, &hc->maxindexname, expnlen + 2 + strlen(index_names[i])); strcpy(hc->indexname, hc->expnfilename); indxlen = strlen(hc->indexname); if (indxlen == 0 || hc->indexname[indxlen - 1] != '/') strcat(hc->indexname, "/"); if (strcmp(hc->indexname, "./") == 0) hc->indexname[0] = '\0'; strcat(hc->indexname, index_names[i]); if (stat(hc->indexname, &hc->sb) >= 0) goto got_one; } /* Nope, no index file, so it's an actual directory request. */ #ifdef GENERATE_INDEXES /* Directories must be readable for indexing. */ if (!(hc->sb.st_mode & S_IROTH)) { syslog(LOG_INFO, "%.80s URL \"%s\" tried to index a directory with indexing disabled", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' resolves to a directory that has indexing disabled.\n"), hc->encodedurl); return -1; } #ifdef ACCESS_FILE /* Check access file for this directory. */ if (access_check(hc, hc->expnfilename) == -1) return -1; #endif #ifdef AUTH_FILE /* Check authorization for this directory. */ if (auth_check(hc, hc->expnfilename) == -1) return -1; #endif /* Referer check. */ if (!check_referer(hc)) return -1; /* Ok, generate an index. */ return ls(hc); #else /* GENERATE_INDEXES */ syslog(LOG_INFO, "%.80s URL \"%s\" tried to index a directory", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' is a directory, and directory indexing is disabled on this server.\n"), hc->encodedurl); return -1; #endif /* GENERATE_INDEXES */ got_one: /* Got an index file. Expand symlinks again. ** More pathinfo means something went wrong. */ cp = expand_symlinks(hc->indexname, &pi, hc->hs->no_symlink_check, hc->tildemapped); if (!cp || pi[0] != '\0') { httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); return -1; } expnlen = strlen(cp); httpd_realloc_str(&hc->expnfilename, &hc->maxexpnfilename, expnlen + 1); strlcpy(hc->expnfilename, cp, hc->maxexpnfilename); /* Now, is the index version world-readable or world-executable? */ if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH))) { syslog(LOG_INFO, "%.80s URL \"%s\" resolves to a non-world-readable index file", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' resolves to an index file that is not world-readable.\n"), hc->encodedurl); return -1; } } else if (!S_ISREG(hc->sb.st_mode)) { /* Err, not a regular file and not a directory? */ httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); return -1; } #ifdef ACCESS_FILE /* Check access for this directory. */ if (access_check(hc, NULL) == -1) return -1; #endif /* ACCESS_FILE */ #ifdef AUTH_FILE /* Check authorization for this directory. */ if (auth_check(hc, NULL) == -1) return -1; #endif /* AUTH_FILE */ sneaky: /* Referer check. */ if (!check_referer(hc)) return -1; if (hc->method == METHOD_OPTIONS) { time_t now; char buf[1000]; char nowbuf[100]; const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; now = time(NULL); strftime(nowbuf, sizeof(nowbuf), rfc1123fmt, gmtime(&now)); snprintf(buf, sizeof(buf), "%.20s %d %s\r\n" "Date: %s\r\n" "Server: %s\r\n" "Allow: %sOPTIONS,GET,HEAD\r\n", hc->protocol, 200, "OK", nowbuf, EXPOSED_SERVER_SOFTWARE, is_cgi(hc) ? "POST," : ""); add_response(hc, buf); if (hc->hs->max_age >= 0) { if (hc->hs->max_age == 0) snprintf(buf, sizeof(buf), "Cache-control: no-cache,no-store\r\n"); else snprintf(buf, sizeof(buf), "Cache-control: max-age=%d\r\n", hc->hs->max_age); add_response(hc, buf); } snprintf(buf, sizeof(buf), "Content-Length: 0\r\n" "Content-Type: text/html\r\n" "\r\n"); add_response(hc, buf); /* HTTP Strict Transport Security: https://www.chromium.org/hsts */ if (hc->ssl) { snprintf(buf, sizeof(buf), "Strict-Transport-Security: " "max-age=%d; includeSubDomains\r\n", hc->hs->max_age < HSTS_MIN_AGE ? HSTS_MIN_AGE : hc->hs->max_age); add_response(hc, buf); } return 0; } /* Is it world-executable and in the CGI area? */ if (is_cgi(hc)) { if (hc->sb.st_mode & S_IXOTH && hc->hs->cgi_enabled) return cgi(hc); if (is_php(hc, NULL) || is_ssi(hc, NULL)) return cgi(hc); syslog(LOG_DEBUG, "%.80s URL \"%s\" is a CGI but not executable, " "or CGI is disabled, rejecting.", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' " "is forbidden.\n"), hc->encodedurl); return -1; } if (hc->pathinfo[0] != '\0') { syslog(LOG_INFO, "%.80s URL \"%s\" has pathinfo but isn't CGI", httpd_client(hc), hc->encodedurl); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "The requested URL '%s' is not a valid CGI.\n"), hc->encodedurl); return -1; } if (hc->method != METHOD_GET && hc->method != METHOD_HEAD) { httpd_send_err(hc, 501, err501title, "", err501form, httpd_method_str(hc->method)); return -1; } /* Fill in last_byte_index, if necessary. */ if (hc->got_range && (hc->last_byte_index == -1 || hc->last_byte_index >= hc->sb.st_size)) hc->last_byte_index = hc->sb.st_size - 1; figure_mime(hc); if (hc->method == METHOD_HEAD) { char *extra = mod_headers(hc); send_mime(hc, 200, ok200title, hc->encodings, extra, hc->type, hc->sb.st_size, hc->sb.st_mtime); } else if (hc->if_modified_since != (time_t)-1 && hc->if_modified_since >= hc->sb.st_mtime) { send_mime(hc, 304, err304title, hc->encodings, "", hc->type, (off_t) - 1, hc->sb.st_mtime); } else { char *extra = mod_headers(hc); hc->file_address = mmc_map(hc->expnfilename, &(hc->sb), now); if (!hc->file_address) { syslog(LOG_ERR, "mmc_map(%s): cannot find %s", hc->expnfilename, is_icon ? "icon" : "file"); if (is_icon) httpd_send_err(hc, 404, err404title, "", err404form, hc->encodedurl); else httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl); return -1; } send_mime(hc, 200, ok200title, hc->encodings, extra, hc->type, hc->sb.st_size, hc->sb.st_mtime); } return 0; } static void make_log_entry(struct http_conn *hc) { char *ru; char url[305]; char bytes[40]; if (hc->hs->no_log) return; /* This is straight CERN Combined Log Format - the only tweak ** being that if we're using syslog() we leave out the date, because ** syslogd puts it in. The included syslogtocern script turns the ** results into true CERN format. */ /* Format remote user. */ if (hc->remoteuser[0] != '\0') ru = hc->remoteuser; else ru = "-"; /* If we're vhosting, prepend the hostname to the url. This is ** a little weird, perhaps writing separate log files for ** each vhost would make more sense. */ if (hc->hs->vhost && !hc->tildemapped) snprintf(url, sizeof(url), "/%.100s%.200s", get_hostname(hc), hc->encodedurl); else snprintf(url, sizeof(url), "%.200s", hc->encodedurl); /* Format the bytes. */ if (hc->bytes_sent >= 0) snprintf(bytes, sizeof(bytes), "%" PRId64, (int64_t)hc->bytes_sent); else strcpy(bytes, "-"); syslog(LOG_INFO, "%.80s: %s \"%s %.200s %s\" %d %s \"%.200s\" \"%.200s\"", httpd_client(hc), ru, httpd_method_str(hc->method), url, hc->protocol, hc->status, bytes, hc->referer, hc->useragent); } /* Returns 1 if ok to serve the url, 0 if not. */ static int check_referer(struct http_conn *hc) { int r; /* Are we doing referer checking at all? */ if (!hc->hs->url_pattern) return 1; r = really_check_referer(hc); if (!r) { syslog(LOG_INFO, "%.80s non-local referer \"%s%s\" \"%s\"", httpd_client(hc), get_hostname(hc), hc->encodedurl, hc->referer); httpd_send_err(hc, 403, err403title, "", ERROR_FORM(err403form, "You must supply a local referer to get URL '%s' from this server.\n"), hc->encodedurl); } return r; } /* Returns 1 if ok to serve the url, 0 if not. */ static int really_check_referer(struct http_conn *hc) { struct httpd *hs; char *cp1; char *cp2; char *cp3; static char *refhost = NULL; static size_t refhost_size = 0; char *lp; hs = hc->hs; /* Check for an empty referer. */ if (!hc->referer || hc->referer[0] == '\0' || (cp1 = strstr(hc->referer, "//")) == NULL) { /* Disallow if we require a referer and the url matches. */ if (hs->no_empty_referers && match(hs->url_pattern, hc->origfilename)) return 0; /* Otherwise ok. */ return 1; } /* Extract referer host. */ cp1 += 2; for (cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2) continue; httpd_realloc_str(&refhost, &refhost_size, cp2 - cp1); for (cp3 = refhost; cp1 < cp2; ++cp1, ++cp3) if (isupper(*cp1)) *cp3 = tolower(*cp1); else *cp3 = *cp1; *cp3 = '\0'; /* Local pattern? */ if (hs->local_pattern) { lp = hs->local_pattern; } else { /* No local pattern. What's our hostname? */ if (!hs->vhost) { /* Not vhosting, use the server name. */ lp = hs->server_hostname; if (!lp) /* Couldn't figure out local hostname - give up. */ return 1; } else { /* We are vhosting, use the hostname on this connection. */ lp = hc->hostname; if (!lp) /* Oops, no hostname. Maybe it's an old browser that ** doesn't send a Host: header. We could figure out ** the default hostname for this IP address, but it's ** not worth it for the few requests like this. */ return 1; } } /* If the referer host doesn't match the local host pattern, and ** the filename does match the url pattern, it's an illegal reference. */ if (!match(lp, refhost) && match(hs->url_pattern, hc->origfilename)) return 0; /* Otherwise ok. */ return 1; } char *httpd_ntoa(sockaddr_t *sa) { #ifdef USE_IPV6 static char str[200]; if (getnameinfo(&sa->sa, sockaddr_len(sa), str, sizeof(str), 0, 0, NI_NUMERICHOST) != 0) { str[0] = '?'; str[1] = '\0'; } else if (IN6_IS_ADDR_V4MAPPED(&sa->sin6.sin6_addr) && strncmp(str, "::ffff:", 7) == 0) { /* Elide IPv6ish prefix for IPv4 addresses. */ memmove(str, &str[7], strlen(str) - 6); } return str; #else return inet_ntoa(sa->sin.sin_addr); #endif } /* If sa is NULL this function can be used to validate the address */ int httpd_aton(char *address, sockaddr_t *sa) { struct addrinfo *res = NULL; struct addrinfo hints; sockaddr_t local; int rc; if (!sa) sa = &local; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; rc = getaddrinfo(address, NULL, &hints, &res); if (rc != 0 || !res) return -1; memcpy(&sa->sa, res->ai_addr, sizeof(sa->sa)); freeaddrinfo(res); /* Fill in sa->address, which should be same as address */ httpd_ntoa(sa); return 0; } short httpd_port(sockaddr_t *sa) { #ifdef USE_IPV6 if (sa->sa.sa_family == AF_INET6) return htons(sa->sin6.sin6_port); #endif return ntohs(sa->sin.sin_port); } char *httpd_client(struct http_conn *hc) { return hc->client.address; } short httpd_client_port(struct http_conn *hc) { return httpd_port(&hc->client); } static int sockaddr_check(sockaddr_t *sa) { switch (sa->sa.sa_family) { case AF_INET: return 1; #ifdef USE_IPV6 case AF_INET6: return 1; #endif default: return 0; } } static size_t sockaddr_len(sockaddr_t *sa) { switch (sa->sa.sa_family) { case AF_INET: return sizeof(struct sockaddr_in); #ifdef USE_IPV6 case AF_INET6: return sizeof(struct sockaddr_in6); #endif default: return 0; /* shouldn't happen */ } } #ifndef HAVE_ATOLL static long long atoll(const char *str) { long long value; long long sign; while (isspace(*str)) ++str; switch (*str) { case '-': sign = -1; ++str; break; case '+': sign = 1; ++str; break; default: sign = 1; break; } value = 0; while (isdigit(*str)) { value = value * 10 + (*str - '0'); ++str; } return sign * value; } #endif /* HAVE_ATOLL */ /* Read the requested buffer completely, accounting for interruptions. */ ssize_t httpd_read(struct http_conn *hc, void *buf, size_t len) { ssize_t rc; if (hc->ssl) return httpd_ssl_read(hc, buf, len); /* Yes, it's a regular read() here, not file_read() */ rc = read(hc->conn_fd, buf, len); if (rc == -1) hc->errmsg = strerror(errno); return rc; } /* Write the requested buffer completely, accounting for interruptions. */ ssize_t httpd_write(struct http_conn *hc, void *buf, size_t len) { ssize_t rc; if (hc->ssl) return httpd_ssl_write(hc, buf, len); rc = file_write(hc->conn_fd, buf, len); if (rc == -1) hc->errmsg = strerror(errno); return rc; } ssize_t httpd_writev(struct http_conn *hc, struct iovec *iov, int num) { ssize_t rc; if (hc->ssl) return httpd_ssl_writev(hc, iov, num); rc = writev(hc->conn_fd, iov, num); if (rc == -1) hc->errmsg = strerror(errno); return rc; } /* Generate debugging statistics syslog message. */ void httpd_logstats(long secs) { if (str_alloc_count <= 0) return; syslog(LOG_INFO, " libhttpd - %d strings allocated, %lu bytes (%g bytes/str)", str_alloc_count, (unsigned long)str_alloc_size, (float)str_alloc_size / str_alloc_count); } merecat-2.31+git20220513+ds/src/libhttpd.h000066400000000000000000000324101424066547300176440ustar00rootroot00000000000000/* libhttpd.h - defines for libhttpd ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef LIBHTTPD_H_ #define LIBHTTPD_H_ #include #include #include #include #include #include #include #include #include #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) #define USE_IPV6 #endif /* A few convenient defines. */ #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #define NEW(t,n) calloc(1, sizeof(t) * (n)) #define RENEW(o,t,n) realloc2((void *)o, sizeof(t) * (n)) /* From The Practice of Programming, by Kernighan and Pike */ #ifndef NELEMS #define NELEMS(array) (sizeof(array) / sizeof(array[0])) #endif /* Doubly linked list macros */ #define LIST_FOREACH(node, list) \ for (typeof (node) next, node = list; \ node && (next = node->next, 1); \ node = next) #define LIST_INSERT(node, list) do { \ typeof (node) next; \ next = list; \ list = node; \ if (next) \ next->prev = node; \ node->next = next; \ node->prev = NULL; \ } while (0) #define LIST_REMOVE(node, list) do { \ typeof (node) prev, next; \ prev = node->prev; \ next = node->next; \ if (prev) \ prev->next = next; \ if (next) \ next->prev = prev; \ node->prev = NULL; \ node->next = NULL; \ if (list == node) \ list = next; \ } while (0) /* The httpd structs. */ /* A multi-family sockaddr. */ typedef struct { union { struct sockaddr sa; struct sockaddr_in sin; #ifdef USE_IPV6 struct sockaddr_in6 sin6; #endif }; char address[200]; /* Real IP address */ } sockaddr_t; /* A redirect. */ struct http_redir { struct http_redir *prev, *next; int code; char *pattern; char *location; }; /* A location. */ struct http_location { struct http_location *prev, *next; char *pattern; char *path; }; /* A server. */ struct httpd { struct httpd *prev, *next; char *binding_hostname; char *server_hostname; unsigned short port; int cgi_enabled; pid_t *cgi_tracker; char *cgi_pattern; int cgi_limit; int cgi_count; char *php_cgi; char *php_pattern; char *ssi_cgi; char *ssi_pattern; char *charset; int max_age; char *cwd; int listen4_fd; int listen6_fd; int no_log; int no_symlink_check; int no_empty_referers; int list_dotfiles; int vhost; int global_passwd; char *url_pattern; char *local_pattern; struct http_redir *redirect; struct http_location *location; void *ctx; /* Opaque SSL_CTX* */ }; /* A connection. */ struct http_conn { int initialized; const char *errmsg; /* General HTTP/HTTPS error message */ struct httpd *hs; sockaddr_t client; char *read_buf; size_t read_size, read_idx, checked_idx; int checked_state; int method; int status; off_t bytes_to_send; off_t bytes_sent; char *encodedurl; char *decodedurl; char *protocol; char *origfilename; char *indexname; char *expnfilename; char *encodings; char *pathinfo; char *query; char *referer; char *useragent; char *accept; char *accepte; char *acceptl; char *cookie; char *contenttype; char *reqhost; char *hdrhost; char *hostdir; char *authorization; char *remoteuser; char *response; size_t maxdecodedurl, maxindexname, maxorigfilename, maxexpnfilename, maxencodings, maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost, maxhostdir, maxremoteuser, maxresponse; #ifdef TILDE_MAP_2 char *altdir; size_t maxaltdir; #endif #ifdef ACCESS_FILE size_t maxaccesspath; char *accesspath; #endif #ifdef AUTH_FILE size_t maxauthpath, maxprevauthpath, maxprevuser, maxprevcryp; char *authpath; char *prevauthpath; char *prevuser; char *prevcryp; #endif size_t responselen; time_t if_modified_since, range_if; size_t contentlength; const char *type; /* not malloc()ed */ char *hostname; /* not malloc()ed */ int mime_flag; int one_one; /* HTTP/1.1 or better */ int got_range; int tildemapped; /* this connection got tilde-mapped */ off_t first_byte_index, last_byte_index; int keep_alive; /* Client signaled */ int do_keep_alive; /* Our intention, which may change */ int should_linger; struct stat sb; int conn_fd; int has_deflate; /* Built with zlib:deflate() and enabled */ int compression_type; char *file_address; void *ssl; /* Opaque SSL* */ int skip_redirect; /* On location match, skip redirect */ }; /* Methods. */ #define METHOD_UNKNOWN 0 #define METHOD_GET 1 #define METHOD_HEAD 2 #define METHOD_POST 3 #define METHOD_PUT 4 #define METHOD_DELETE 5 #define METHOD_CONNECT 6 #define METHOD_OPTIONS 7 #define METHOD_TRACE 8 /* States for checked_state. */ #define CHST_FIRSTWORD 0 #define CHST_FIRSTWS 1 #define CHST_SECONDWORD 2 #define CHST_SECONDWS 3 #define CHST_THIRDWORD 4 #define CHST_THIRDWS 5 #define CHST_LINE 6 #define CHST_LF 7 #define CHST_CR 8 #define CHST_CRLF 9 #define CHST_CRLFCR 10 #define CHST_BOGUS 11 /* For content-encoding: gzip */ #define COMPRESSION_NONE 0 #define COMPRESSION_GZIP 1 /* Initializes main HTTPD server. Returns NULL on error. */ extern struct httpd *httpd_init(char *hostname, unsigned short port, void *ssl_ctx, char *charset, int max_age, char *cwd, int no_log, int no_symlink_check, int vhost, int global_passwd, char *url_pattern, char *local_pattern, int no_empty_referers, int list_dotfiles); /* Enable CGI/1.1 support */ extern int httpd_cgi_init(struct httpd *hs, int enabled, char *cgi_pattern, int cgi_limit); /* Enable HTTP redirect -- Note: O(n) lookup per HTTP request */ extern int httpd_redirect_add(struct httpd *hs, int code, char *pattern, char *location); /* Server location matching, overrides httpd cwd on match */ extern int httpd_location_add(struct httpd *hs, char *pattern, char *path); /* Start httpd */ extern int httpd_listen(struct httpd *hs, sockaddr_t *sav4, sockaddr_t *sav6); /* Call to shut down. */ extern void httpd_exit(struct httpd *hs); /* Call to unlisten/close socket(s) listening for new connections. */ extern void httpd_unlisten(struct httpd *hs); /* Used to reinitialize the connection for pipelined keep-alive requets */ extern void httpd_init_conn_mem(struct http_conn *hc); extern void httpd_init_conn_content(struct http_conn *hc); /* When a listen fd is ready to be read, call this. It does the accept() and ** returns a struct http_conn* which includes the fd to read the request from ** and write the response to. Returns an indication of whether the accept() ** failed, succeeded, or if there were no more connections to accept. ** ** In order to minimize malloc()s, the caller passes in the struct http_conn. ** The caller is also responsible for setting initialized to zero before the ** first call using each different struct http_conn. */ extern int httpd_get_conn(struct httpd *hs, int listen_fd, struct http_conn *hc); #define GC_FAIL 0 #define GC_OK 1 #define GC_NO_MORE 2 /* Checks whether the data in hc->read_buf constitutes a complete request ** yet. The caller reads data into hc->read_buf[hc->read_idx] and advances ** hc->read_idx. This routine checks what has been read so far, using ** hc->checked_idx and hc->checked_state to keep track, and returns an ** indication of whether there is no complete request yet, there is a ** complete request, or there won't be a valid request due to a syntax error. */ extern int httpd_got_request(struct http_conn *hc); #define GR_NO_REQUEST 0 #define GR_GOT_REQUEST 1 #define GR_BAD_REQUEST 2 /* Parses the request in hc->read_buf. Fills in lots of fields in hc, ** like the URL and the various headers. ** ** Returns -1 on error. */ extern int httpd_parse_request(struct http_conn *hc); /* Starts sending data back to the client. In some cases (directories, ** CGI programs), finishes sending by itself - in those cases, hc->file_fd ** is <0. If there is more data to be sent, then hc->file_fd is a file ** descriptor for the file to send. If you don't have a current timeval ** handy just pass in 0. ** ** Returns -1 on error. */ extern int httpd_start_request(struct http_conn *hc, struct timeval *now); /* Actually sends any buffered response text. */ extern void httpd_send_response(struct http_conn *hc); /* Call this to close down a connection and free the data. A fine point, ** if you fork() with a connection open you should still call this in the ** parent process - the connection will stay open in the child. ** If you don't have a current timeval handy just pass in 0. */ extern void httpd_close_conn(struct http_conn *hc, struct timeval *now); /* Call this to de-initialize a connection struct and *really* free the ** mallocced strings. */ extern void httpd_destroy_conn(struct http_conn *hc); /* Client IP addresses can be overridden by a proxy using X-Forwarded-For */ extern char *httpd_client(struct http_conn *hc); /* Client port */ extern short httpd_client_port(struct http_conn *hc); /* Send an error message back to the client. */ extern void httpd_send_err(struct http_conn *hc, int status, char *title, const char *extraheads, char *form, char *arg); /* Some error messages. */ extern char *httpd_err400title; extern char *httpd_err400form; extern char *httpd_err408title; extern char *httpd_err408form; extern char *httpd_err503title; extern char *httpd_err503form; /* Generate a string representation of a method number. */ extern char *httpd_method_str(int method); /* Reallocate a string. */ extern void httpd_realloc_str(char **str, size_t *curr_len, size_t new_len); /* Format a network socket to a string representation. */ extern char *httpd_ntoa(sockaddr_t *sa); /* Convert string address to socket address */ extern int httpd_aton(char *address, sockaddr_t *sa); /* Return port from sockaddr */ extern short httpd_port(sockaddr_t *sa); /* Set NDELAY mode on a socket. */ extern int httpd_set_ndelay(int sd); /* Clear NDELAY mode on a socket. */ extern int httpd_clear_ndelay(int sd); /* Read the requested buffer completely, accounting for interruptions. */ extern ssize_t httpd_read(struct http_conn *hc, void *buf, size_t len); /* Write the requested buffer completely, accounting for interruptions. */ extern ssize_t httpd_write(struct http_conn *hc, void *buf, size_t len); extern ssize_t httpd_writev(struct http_conn *hc, struct iovec *iov, int num); /* Generate debugging statistics syslog message. */ extern void httpd_logstats(long secs); /* Track PID of CGI scripts, server calls untrack for each collected PID */ extern int httpd_cgi_track(struct httpd *hs, pid_t pid); extern int httpd_cgi_untrack(struct httpd *hs, pid_t pid); /* ** Default CSS used in error pages */ static inline const char *httpd_css_default(void) { const char *style = " \n"; return style; } /* ** Handle common error usage with realloc(), for RENEW() macro */ static inline void *realloc2(void *old, size_t len) { void *ptr; ptr = realloc(old, len); if (!ptr) free(old); return ptr; } #endif /* LIBHTTPD_H_ */ merecat-2.31+git20220513+ds/src/make_mime.pl000077500000000000000000000016131424066547300201460ustar00rootroot00000000000000#!/usr/bin/perl #Run this on developer side, whenever you update #your mime encodings, or mime types. open(ENCODINGS, '<', "mime_encodings.txt"); @encoding=; close(ENCODINGS); open(ENCHEADER, '>', "mime_encodings.h"); foreach (@encoding) { chomp($_); @element = split(/\t+/,$_); next if $element[0] =~ /#/ ; next if $element[1] =~ /#/ ; next if length($element[0]) == 0 || length($element[1]) == 0 ; print ENCHEADER '{ "', $element[0], '", 0, "', $element[1], '", 0 },', "\n"; } close(ENCHEADER); open(TYPES, '<', "mime_types.txt"); @type=; close(TYPES); open(TYPEHEADER, '>', "mime_types.h"); foreach (@type) { chomp($_); @element = split(/\t+/,$_); next if $element[0] =~ /#/ ; next if $element[1] =~ /#/ ; next if length($element[0]) == 0 || length($element[1]) == 0 ; print TYPEHEADER '{ "', $element[0], '", 0, "', $element[1], '", 0 },', "\n"; } close(TYPEHEADER); merecat-2.31+git20220513+ds/src/match.c000066400000000000000000000052131424066547300171220ustar00rootroot00000000000000/* match.c - simple shell-style filename matcher ** ** Only does ? * and **, and multiple patterns separated by |. Returns 1 or 0. ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "match.h" static int match_one(const char *pattern, int patternlen, const char *string); int match(const char *pattern, const char *string) { if (!pattern) return 0; for (;;) { const char *or; int rc; or = strchr(pattern, '|'); if (!or) return match_one(pattern, strlen(pattern), string); rc = match_one(pattern, or - pattern, string); if (rc) return rc; pattern = or + 1; } } static int match_one(const char *pattern, int patternlen, const char *string) { const char *p, *s; for (p = pattern, s = string; p - pattern < patternlen; ++p, ++s) { if (*p == '?' && *s != '\0') continue; if (*p == '*') { int i, pl; ++p; if (*p == '*') { /* Double-wildcard matches anything. */ ++p; i = strlen(s); } else { /* Single-wildcard matches anything but slash. */ i = strcspn(s, "/"); } pl = patternlen - (p - pattern); for (; i >= 0; --i) { if (match_one(p, pl, &(s[i]))) { int len = s - string; return len > 1 ? len : 1; } } return 0; } if (*p != *s) return 0; } if (*s == '\0') return 1; return 0; } merecat-2.31+git20220513+ds/src/match.h000066400000000000000000000032411424066547300171260ustar00rootroot00000000000000/* match.h - simple shell-style filename patcher ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MATCH_H_ #define MATCH_H_ /* Simple shell-style filename pattern matcher. Only does ? * and **, and ** multiple patterns separated by |. Returns 1 or 0. */ extern int match(const char *pattern, const char *string); #endif /* MATCH_H_ */ merecat-2.31+git20220513+ds/src/md5.c000066400000000000000000000174131424066547300165200ustar00rootroot00000000000000/* $OpenBSD: md5.c,v 1.11 2015/09/11 09:18:27 guenther Exp $ */ /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ #include #include #include "md5.h" #define PUT_64BIT_LE(cp, value) do { \ (cp)[7] = (value) >> 56; \ (cp)[6] = (value) >> 48; \ (cp)[5] = (value) >> 40; \ (cp)[4] = (value) >> 32; \ (cp)[3] = (value) >> 24; \ (cp)[2] = (value) >> 16; \ (cp)[1] = (value) >> 8; \ (cp)[0] = (value); } while (0) #define PUT_32BIT_LE(cp, value) do { \ (cp)[3] = (value) >> 24; \ (cp)[2] = (value) >> 16; \ (cp)[1] = (value) >> 8; \ (cp)[0] = (value); } while (0) static u_int8_t PADDING[MD5_BLOCK_LENGTH] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious * initialization constants. */ void MD5Init(MD5_CTX *ctx) { ctx->count = 0; ctx->state[0] = 0x67452301; ctx->state[1] = 0xefcdab89; ctx->state[2] = 0x98badcfe; ctx->state[3] = 0x10325476; } /* * Update context to reflect the concatenation of another buffer full * of bytes. */ void MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len) { size_t have, need; /* Check how many bytes we already have and how many more we need. */ have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); need = MD5_BLOCK_LENGTH - have; /* Update bitcount */ ctx->count += (u_int64_t)len << 3; if (len >= need) { if (have != 0) { memcpy(ctx->buffer + have, input, need); MD5Transform(ctx->state, ctx->buffer); input += need; len -= need; have = 0; } /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ while (len >= MD5_BLOCK_LENGTH) { MD5Transform(ctx->state, input); input += MD5_BLOCK_LENGTH; len -= MD5_BLOCK_LENGTH; } } /* Handle any remaining bytes of data. */ if (len != 0) memcpy(ctx->buffer + have, input, len); } /* * Pad pad to 64-byte boundary with the bit pattern * 1 0* (64-bit count of bits processed, MSB-first) */ void MD5Pad(MD5_CTX *ctx) { u_int8_t count[8]; size_t padlen; /* Convert count to 8 bytes in little endian order. */ PUT_64BIT_LE(count, ctx->count); /* Pad out to 56 mod 64. */ padlen = MD5_BLOCK_LENGTH - ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); if (padlen < 1 + 8) padlen += MD5_BLOCK_LENGTH; MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ MD5Update(ctx, count, 8); } /* * Final wrapup--call MD5Pad, fill in digest and zero out ctx. */ void MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx) { int i; MD5Pad(ctx); for (i = 0; i < 4; i++) PUT_32BIT_LE(digest + i * 4, ctx->state[i]); memset(ctx, 0, sizeof(*ctx)); } /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) /* This is the central step in the MD5 algorithm. */ #define MD5STEP(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) /* * The core of the MD5 algorithm, this alters an existing MD5 hash to * reflect the addition of 16 longwords of new data. MD5Update blocks * the data and converts bytes into longwords for this routine. */ void MD5Transform(u_int32_t state[4], const u_int8_t block[MD5_BLOCK_LENGTH]) { u_int32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; #if BYTE_ORDER == LITTLE_ENDIAN memcpy(in, block, sizeof(in)); #else for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { in[a] = (u_int32_t)( (u_int32_t)(block[a * 4 + 0]) | (u_int32_t)(block[a * 4 + 1]) << 8 | (u_int32_t)(block[a * 4 + 2]) << 16 | (u_int32_t)(block[a * 4 + 3]) << 24); } #endif a = state[0]; b = state[1]; c = state[2]; d = state[3]; MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); state[0] += a; state[1] += b; state[2] += c; state[3] += d; } merecat-2.31+git20220513+ds/src/md5.h000066400000000000000000000024011424066547300165140ustar00rootroot00000000000000/* $OpenBSD: md5.h,v 1.17 2012/12/05 23:19:57 deraadt Exp $ */ /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. */ #ifndef _MD5_H_ #define _MD5_H_ #define MD5_BLOCK_LENGTH 64 #define MD5_DIGEST_LENGTH 16 #define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1) typedef struct MD5Context { u_int32_t state[4]; /* state */ u_int64_t count; /* number of bits, mod 2^64 */ u_int8_t buffer[MD5_BLOCK_LENGTH]; /* input buffer */ } MD5_CTX; void MD5Init(MD5_CTX *); void MD5Update(MD5_CTX *, const u_int8_t *, size_t); void MD5Pad(MD5_CTX *); void MD5Final(u_int8_t [MD5_DIGEST_LENGTH], MD5_CTX *); void MD5Transform(u_int32_t [4], const u_int8_t [MD5_BLOCK_LENGTH]); char *MD5End(MD5_CTX *, char *); char *MD5File(const char *, char *); char *MD5FileChunk(const char *, char *, off_t, off_t); char *MD5Data(const u_int8_t *, size_t, char *); #endif /* _MD5_H_ */ merecat-2.31+git20220513+ds/src/merecat.c000066400000000000000000001305411424066547300174510ustar00rootroot00000000000000/* Simple, small and fast HTTP server ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #ifdef HAVE_GRP_H #include #endif #include #include #define SYSLOG_NAMES #include #include #include #include #include #include #ifdef HAVE_ZLIB_H #include #endif #include "conf.h" #include "fdwatch.h" #include "libhttpd.h" #include "match.h" #include "mmc.h" #include "merecat.h" #include "srv.h" #include "ssl.h" #include "timers.h" #ifndef SHUT_WR #define SHUT_WR 1 #endif /* For content-encoding: gzip */ #ifdef HAVE_ZLIB_H #define ZLIB_OUTPUT_BUF_SIZE 262136 #define DEFAULT_COMPRESSION Z_DEFAULT_COMPRESSION #else #define DEFAULT_COMPRESSION 0 #endif char *prognm; /* Instead of non-portable __progname */ char *ident; /* Used for logging */ int loglevel = LOG_NOTICE; int dbglevel = LOG_DEBUG; char path[MAXPATHLEN + 1]; /* Global config settings */ uint16_t port = 0; int max_age = 0; /* Disabled globally since v2.32 */ int compression_level = DEFAULT_COMPRESSION; /* For content-encoding: gzip */ int do_chroot = 0; int do_vhost = 0; int do_global_passwd = 0; int do_list_dotfiles = 0; int no_symlink_check = 1; int no_empty_referers = 0; int cgi_enabled = 0; int cgi_limit = CGI_LIMIT; char *cgi_pattern = CGI_PATTERN; char *local_pattern = NULL; char *php_cgi = NULL; char *php_pattern = NULL; char *ssi_cgi = NULL; int ssi_silent = 0; char *ssi_pattern = NULL; char *url_pattern = NULL; char *dir = NULL; char *data_dir = NULL; char *hostname = NULL; char *user = DEFAULT_USER; /* Usually www-data or nobody */ char *charset = DEFAULT_CHARSET; char *useragent_deny = NULL; /* Global options */ static char *throttlefile = NULL; typedef struct { char *pattern; long max_limit, min_limit; long rate; off_t bytes_since_avg; int num_sending; } throttletab; static throttletab *throttles; static int numthrottles, maxthrottles; #define THROTTLE_NOLIMIT -1 typedef struct { int conn_state; int next_free_connect; struct http_conn *hc; int tnums[MAXTHROTTLENUMS]; /* throttle indexes */ int numtnums; long max_limit, min_limit; time_t started_at, active_at; struct timer *wakeup_timer; struct timer *linger_timer; long wouldblock_delay; off_t bytes; off_t end_byte_index; off_t next_byte_index; #ifdef HAVE_ZLIB_H z_stream zs; int zs_state; void *zs_output_head; #endif } connecttab; static connecttab *connects; static int num_connects, max_connects, first_free_connect; static int httpd_conn_count; /* The connection states. */ #define CNST_FREE 0 #define CNST_READING 1 #define CNST_SENDING 2 #define CNST_PAUSING 3 #define CNST_LINGERING 4 static struct httpd *server_list = NULL; int terminate = 0; time_t start_time, stats_time; long stats_connections; off_t stats_bytes; int stats_simultaneous; /* Configured by conf_srv(), or globals if no .conf support */ static struct srv srvtab[2]; static volatile int got_hup, got_bus, got_usr1, watchdog_flag; /* External functions */ extern int pidfile(const char *basename); static void read_throttlefile(char *throttlefile) { FILE *fp; char buf[5000]; char *cp; int len; char pattern[5000]; long max_limit, min_limit; struct timeval tv; fp = fopen(throttlefile, "r"); if (!fp) { syslog(LOG_CRIT, "%s: %s", throttlefile, strerror(errno)); exit(1); } gettimeofday(&tv, NULL); while (fgets(buf, sizeof(buf), fp)) { /* Nuke comments. */ cp = strchr(buf, '#'); if (cp) *cp = '\0'; /* Nuke trailing whitespace. */ len = strlen(buf); while (len > 0 && (buf[len - 1] == ' ' || buf[len - 1] == '\t' || buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = '\0'; /* Ignore empty lines. */ if (len == 0) continue; /* Parse line. */ if (sscanf(buf, " %4900[^ \t] %ld-%ld", pattern, &min_limit, &max_limit) == 3) { } else if (sscanf(buf, " %4900[^ \t] %ld", pattern, &max_limit) == 2) min_limit = 0; else { syslog(LOG_ERR, "unparsable line in %s: %s", throttlefile, buf); continue; } /* Nuke any leading slashes in pattern. */ if (pattern[0] == '/') memmove(pattern, &pattern[1], strlen(pattern)); while ((cp = strstr(pattern, "|/"))) memmove(cp + 1, cp + 2, strlen(cp) - 1); /* Check for room in throttles. */ if (numthrottles >= maxthrottles) { if (maxthrottles == 0) { maxthrottles = 100; /* arbitrary */ throttles = NEW(throttletab, maxthrottles); } else { maxthrottles *= 2; throttles = RENEW(throttles, throttletab, maxthrottles); } if (!throttles) { syslog(LOG_CRIT, "Out of memory allocating a throttletab"); exit(1); } } /* Add to table. */ throttles[numthrottles].pattern = strdup(pattern); if (!throttles[numthrottles].pattern) { syslog(LOG_CRIT, "Failed storing throttle pattern: %s", strerror(errno)); exit(1); } throttles[numthrottles].max_limit = max_limit; throttles[numthrottles].min_limit = min_limit; throttles[numthrottles].rate = 0; throttles[numthrottles].bytes_since_avg = 0; throttles[numthrottles].num_sending = 0; ++numthrottles; } (void)fclose(fp); } /* Generate debugging statistics syslog message. */ static void merecat_logstats(long secs) { if (secs > 0) syslog(LOG_INFO, " %s - %ld connections (%g/sec), %d max simultaneous, %ld bytes (%g/sec), %d httpd_conns allocated", PACKAGE_NAME, stats_connections, (float)stats_connections / secs, stats_simultaneous, (long int)stats_bytes, (float)stats_bytes / secs, httpd_conn_count); stats_connections = 0; stats_bytes = 0; stats_simultaneous = 0; } /* Generate debugging statistics syslog messages for all packages. */ static void logstats(struct timeval *now) { struct timeval tv; time_t sec; long up_secs, stats_secs; if (!now) { gettimeofday(&tv, NULL); now = &tv; } sec = now->tv_sec; up_secs = sec - start_time; stats_secs = sec - stats_time; if (stats_secs == 0) stats_secs = 1; /* fudge */ stats_time = sec; syslog(LOG_INFO, "up %ld seconds, stats for %ld seconds:", up_secs, stats_secs); merecat_logstats(stats_secs); httpd_logstats(stats_secs); mmc_logstats(stats_secs); fdwatch_logstats(stats_secs); tmr_logstats(stats_secs); } static void shut_down(void) { struct httpd *server; struct timeval tv; int i; gettimeofday(&tv, NULL); logstats(&tv); for (i = 0; i < max_connects; ++i) { if (connects[i].conn_state != CNST_FREE) httpd_close_conn(connects[i].hc, &tv); if (connects[i].hc) { httpd_destroy_conn(connects[i].hc); free(connects[i].hc); connects[i].hc = NULL; --httpd_conn_count; } } LIST_FOREACH(server, server_list) { LIST_REMOVE(server, server_list); srv_exit(server); } conf_exit(); fdwatch_put_nfiles(); mmc_destroy(); tmr_destroy(); free(connects); if (throttles) free(throttles); } static int check_throttles(connecttab *c) { int tnum; long l; c->numtnums = 0; c->max_limit = c->min_limit = THROTTLE_NOLIMIT; for (tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS; ++tnum) { if (match(throttles[tnum].pattern, c->hc->expnfilename)) { /* If we're way over the limit, don't even start. */ if (throttles[tnum].rate > throttles[tnum].max_limit * 2) return 0; /* Also don't start if we're under the minimum. */ if (throttles[tnum].rate < throttles[tnum].min_limit) return 0; if (throttles[tnum].num_sending < 0) { syslog(LOG_ERR, "throttle sending count was negative - shouldn't happen!"); throttles[tnum].num_sending = 0; } c->tnums[c->numtnums++] = tnum; ++throttles[tnum].num_sending; l = throttles[tnum].max_limit / throttles[tnum].num_sending; if (c->max_limit == THROTTLE_NOLIMIT) c->max_limit = l; else c->max_limit = MIN(c->max_limit, l); l = throttles[tnum].min_limit; if (c->min_limit == THROTTLE_NOLIMIT) c->min_limit = l; else c->min_limit = MAX(c->min_limit, l); } } return 1; } static void clear_throttles(connecttab *c, struct timeval *tv) { int tind; for (tind = 0; tind < c->numtnums; ++tind) --throttles[c->tnums[tind]].num_sending; } static void update_throttles(arg_t arg, struct timeval *now) { int tnum, tind; int cnum; connecttab *c; long l; /* Update the average sending rate for each throttle. ** This is only used when new connections start up. */ for (tnum = 0; tnum < numthrottles; ++tnum) { throttles[tnum].rate = (2 * throttles[tnum].rate + throttles[tnum].bytes_since_avg / THROTTLE_TIME) / 3; throttles[tnum].bytes_since_avg = 0; /* Log a warning message if necessary. */ if (throttles[tnum].rate > throttles[tnum].max_limit && throttles[tnum].num_sending != 0) { if (throttles[tnum].rate > throttles[tnum].max_limit * 2) syslog(LOG_NOTICE, "throttle #%d '%s' rate %ld greatly exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending); else syslog(LOG_INFO, "throttle #%d '%s' rate %ld exceeding limit %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].max_limit, throttles[tnum].num_sending); } if (throttles[tnum].rate < throttles[tnum].min_limit && throttles[tnum].num_sending != 0) { syslog(LOG_NOTICE, "throttle #%d '%s' rate %ld lower than minimum %ld; %d sending", tnum, throttles[tnum].pattern, throttles[tnum].rate, throttles[tnum].min_limit, throttles[tnum].num_sending); } } /* Now update the sending rate on all the currently-sending ** connections, redistributing it evenly. */ for (cnum = 0; cnum < max_connects; ++cnum) { c = &connects[cnum]; if (c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING) { c->max_limit = THROTTLE_NOLIMIT; for (tind = 0; tind < c->numtnums; ++tind) { tnum = c->tnums[tind]; l = throttles[tnum].max_limit / throttles[tnum].num_sending; if (c->max_limit == THROTTLE_NOLIMIT) c->max_limit = l; else c->max_limit = MIN(c->max_limit, l); } } } } static void really_clear_connection(connecttab *c, struct timeval *tv) { stats_bytes += c->hc->bytes_sent; if (c->conn_state != CNST_PAUSING) fdwatch_del_fd(c->hc->conn_fd); httpd_close_conn(c->hc, tv); clear_throttles(c, tv); if (c->linger_timer) { tmr_cancel(c->linger_timer); c->linger_timer = 0; } #ifdef HAVE_ZLIB_H if (c->zs_output_head) { free(c->zs_output_head); c->zs_output_head = NULL; deflateEnd(&c->zs); } #endif c->conn_state = CNST_FREE; c->next_free_connect = first_free_connect; first_free_connect = c - connects; /* division by sizeof is implied */ --num_connects; } static void wakeup_connection(arg_t arg, struct timeval *now) { connecttab *c; c = (connecttab *)arg.p; c->wakeup_timer = NULL; if (c->conn_state == CNST_PAUSING) { c->conn_state = CNST_SENDING; fdwatch_add_fd(c->hc->conn_fd, c, FDW_WRITE); } } static void linger_clear_connection(arg_t arg, struct timeval *now) { connecttab *c; c = (connecttab *)arg.p; c->hc->do_keep_alive = 0; c->linger_timer = NULL; really_clear_connection(c, now); } static void clear_connection(connecttab *c, struct timeval *tv) { arg_t arg; if (c->wakeup_timer) { tmr_cancel(c->wakeup_timer); c->wakeup_timer = 0; } /* This is our version of Apache's lingering_close() routine, which is ** their version of the often-broken SO_LINGER socket option. For why ** this is necessary, see [1]. What we do is delay the actual closing ** for a few seconds, while reading any bytes that come over the ** connection. However, we don't want to do this unless it's ** necessary, because it ties up a connection slot and file descriptor ** which means our maximum connection-handling rate is lower. So, ** elsewhere we set a flag when we detect the few circumstances that ** make a lingering close necessary. If the flag isn't set we do the ** real close now. ** ** [1]: http://www.apache.org/docs/misc/fin_wait_2.html */ if (c->conn_state == CNST_LINGERING && !c->hc->do_keep_alive) { /* If we were already lingering, shut down for real. */ tmr_cancel(c->linger_timer); c->linger_timer = NULL; c->hc->should_linger = 0; } if (c->hc->do_keep_alive) { if (c->conn_state != CNST_PAUSING) fdwatch_del_fd(c->hc->conn_fd); fdwatch_add_fd(c->hc->conn_fd, c, FDW_READ); c->conn_state = CNST_READING; c->next_byte_index = 0; arg.p = c; if (c->linger_timer) tmr_cancel(c->linger_timer); /* release file memory */ if (c->hc->file_address) { mmc_unmap(c->hc->file_address, &c->hc->sb, tv); c->hc->file_address = NULL; } /* release httpd_conn auxiliary memory */ httpd_destroy_conn(c->hc); /* reinitialize httpd_conn */ httpd_init_conn_mem(c->hc); httpd_init_conn_content(c->hc); /* Reset the connection file descriptor to no-delay mode. */ (void)httpd_set_ndelay(c->hc->conn_fd); c->linger_timer = tmr_create(tv, linger_clear_connection, arg, KEEPALIVE_TIMELIMIT, 0); if (!c->linger_timer) { syslog(LOG_CRIT, "tmr_create(linger_clear_connection)2 failed"); exit(1); } } else if (c->hc->should_linger) { if (c->conn_state != CNST_PAUSING) fdwatch_del_fd(c->hc->conn_fd); c->conn_state = CNST_LINGERING; shutdown(c->hc->conn_fd, SHUT_WR); fdwatch_add_fd(c->hc->conn_fd, c, FDW_READ); arg.p = c; if (c->linger_timer) { tmr_reset(tv, c->linger_timer); } else { c->linger_timer = tmr_create(tv, linger_clear_connection, arg, LINGER_TIME, 0); if (!c->linger_timer) { syslog(LOG_CRIT, "tmr_create(linger_clear_connection) failed"); exit(1); } } } else { really_clear_connection(c, tv); } } static void finish_connection(connecttab *c, struct timeval *tv) { /* If we haven't actually sent the buffered response yet, do so now. */ httpd_send_response(c->hc); /* And clear. */ clear_connection(c, tv); } int handle_newconnect(struct httpd *hs, struct timeval *tv, int fd) { connecttab *c; /* This loops until the accept() fails, trying to start new ** connections as fast as possible so we don't overrun the ** listen queue. */ for (;;) { /* Is there room in the connection table? */ if (num_connects >= max_connects) { /* Out of connection slots. Run the timers, then the ** existing connections, and maybe we'll free up a slot ** by the time we get back here. */ syslog(LOG_WARNING, "Too many connections (%d >) %d)!", num_connects, max_connects); tmr_run(tv); return 0; } /* Get the first free connection entry off the free list. */ if (first_free_connect == -1 || connects[first_free_connect].conn_state != CNST_FREE) { syslog(LOG_CRIT, "The connects free list is messed up"); exit(1); } /* Make the httpd_conn if necessary. */ c = &connects[first_free_connect]; if (!c->hc) { c->hc = NEW(struct http_conn, 1); if (!c->hc) { syslog(LOG_CRIT, "Out of memory allocating an httpd_conn"); exit(1); } c->hc->initialized = 0; ++httpd_conn_count; } /* Get the connection. */ switch (httpd_get_conn(hs, fd, c->hc)) { /* Some error happened. Run the timers, then the ** existing connections. Maybe the error will clear. */ case GC_FAIL: tmr_run(tv); return 0; /* No more connections to accept for now. */ case GC_NO_MORE: return 1; } c->conn_state = CNST_READING; /* Pop it off the free list. */ first_free_connect = c->next_free_connect; c->next_free_connect = -1; ++num_connects; c->active_at = tv->tv_sec; c->wakeup_timer = NULL; c->linger_timer = NULL; c->next_byte_index = 0; c->numtnums = 0; /* Set the connection file descriptor to no-delay mode. */ (void)httpd_set_ndelay(c->hc->conn_fd); fdwatch_add_fd(c->hc->conn_fd, c, FDW_READ); ++stats_connections; if (num_connects > stats_simultaneous) stats_simultaneous = num_connects; } } static void handle_read(connecttab *c, struct timeval *tv) { int sz; struct http_conn *hc = c->hc; /* Is there room in our buffer to read more bytes? */ if (hc->read_idx >= hc->read_size) { if (hc->read_size > 5000) { httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, ""); finish_connection(c, tv); return; } httpd_realloc_str(&hc->read_buf, &hc->read_size, hc->read_size + 1000); } /* Read some more bytes. */ sz = httpd_read(hc, &(hc->read_buf[hc->read_idx]), hc->read_size - hc->read_idx); if (sz == 0) { // if (!hc->do_keep_alive) // httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, ""); if (hc->do_keep_alive) hc->do_keep_alive--; c->active_at = tv->tv_sec; finish_connection(c, tv); return; } if (sz < 0) { /* Ignore EINTR and EAGAIN. Also ignore EWOULDBLOCK. At first glance ** you would think that connections returned by fdwatch as readable ** should never give an EWOULDBLOCK; however, this apparently can ** happen if a packet gets garbled. */ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return; // httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, ""); finish_connection(c, tv); return; } hc->read_idx += sz; c->active_at = tv->tv_sec; /* Do we have a complete request yet? */ switch (httpd_got_request(hc)) { case GR_NO_REQUEST: return; case GR_BAD_REQUEST: // httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, ""); finish_connection(c, tv); return; } /* Must tell libhttpd if we can deflate files */ #ifdef HAVE_ZLIB_H hc->has_deflate = compression_level != 0; #else hc->has_deflate = 0; #endif /* Yes. Try parsing and resolving it. */ if (httpd_parse_request(hc) < 0) { finish_connection(c, tv); return; } /* Check the throttle table */ if (!check_throttles(c)) { httpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl); finish_connection(c, tv); return; } /* Start the connection going. */ if (httpd_start_request(hc, tv) < 0) { /* Something went wrong. Close down the connection. */ finish_connection(c, tv); return; } /* Fill in end_byte_index. */ if (hc->got_range) { c->next_byte_index = hc->first_byte_index; c->end_byte_index = hc->last_byte_index + 1; } else if (hc->bytes_to_send < 0) { c->end_byte_index = 0; } else { c->end_byte_index = hc->bytes_to_send; } /* Check if it's already handled. */ if (!hc->file_address) { /* No file address means someone else is handling it. */ int tind; for (tind = 0; tind < c->numtnums; ++tind) throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent; c->next_byte_index = hc->bytes_sent; finish_connection(c, tv); return; } if (c->next_byte_index >= c->end_byte_index) { /* There's nothing to send. */ finish_connection(c, tv); return; } /* Cool, we have a valid connection and a file to send to it. */ c->conn_state = CNST_SENDING; c->started_at = tv->tv_sec; c->wouldblock_delay = 0; #ifdef HAVE_ZLIB_H if (hc->compression_type != COMPRESSION_NONE) { /* setup default zlib memory allocation routines */ c->zs.zalloc = Z_NULL; c->zs.zfree = Z_NULL; c->zs.opaque = Z_NULL; /* setup zlib input file to mmap'ed location */ c->zs.next_in = (Bytef *)c->hc->file_address; c->zs.avail_in = c->hc->sb.st_size; /* allocate memory for output buffer, if it's not already allocated */ if (!c->zs_output_head) { c->zs_output_head = malloc(ZLIB_OUTPUT_BUF_SIZE + 8); if (!c->zs_output_head) { syslog(LOG_CRIT, "out of memory allocating deflate buffer"); exit(1); } } if (hc->compression_type == COMPRESSION_GZIP) { /* add gzip header to output file */ sprintf(c->zs_output_head, "%c%c%c%c%c%c%c%c%c%c", 0x1f, 0x8b, Z_DEFLATED, 0 /*flags*/, #if 0 /* Seems to be optional according to https://tools.ietf.org/html/rfc1952 */ &c->hc->sb.st_mtime, /* XXX: use a more transportable implementation! */ &c->hc->sb.st_mtime + 1, &c->hc->sb.st_mtime + 2, &c->hc->sb.st_mtime + 3, #else 0, 0, 0, 0, #endif 0 /*xflags*/, 0x03); c->zs.next_out = c->zs_output_head + 10; c->zs.avail_out = ZLIB_OUTPUT_BUF_SIZE - 10; } /* call the initialization for zlib with negative window ** size to omit the "deflate" prefix */ c->zs_state = deflateInit2(&c->zs, compression_level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); if (c->zs_state != Z_OK) { syslog(LOG_CRIT, "zlib deflateInit2() failed!"); exit(1); } } #endif /* HAVE_ZLIB_H */ fdwatch_del_fd(hc->conn_fd); fdwatch_add_fd(hc->conn_fd, c, FDW_WRITE); } static void handle_send(connecttab *c, struct timeval *tv) { size_t max_bytes; ssize_t sz = -1; int coast; arg_t arg; time_t elapsed; struct http_conn *hc = c->hc; int tind; if (c->max_limit == THROTTLE_NOLIMIT) max_bytes = 1000000000L; else max_bytes = c->max_limit / 4; /* send at most 1/4 seconds worth */ if (hc->compression_type == COMPRESSION_NONE) { /* Do we need to write the headers first? */ if (hc->responselen == 0) { /* No, just write the file. */ sz = httpd_write(hc, &(hc->file_address[c->next_byte_index]), MIN(c->end_byte_index - c->next_byte_index, (off_t)max_bytes)); } else { /* Yes. We'll combine headers and file into a single writev(), ** hoping that this generates a single packet. */ struct iovec iv[2]; iv[0].iov_base = hc->response; iv[0].iov_len = hc->responselen; iv[1].iov_base = &(hc->file_address[c->next_byte_index]); iv[1].iov_len = MIN(c->end_byte_index - c->next_byte_index, (off_t)max_bytes); sz = httpd_writev(hc, iv, 2); } #ifdef HAVE_ZLIB_H } else { int iv_count; struct iovec iv[2]; /* call deflate only if necessary */ if ((c->zs_state == Z_OK) && (c->zs.avail_out > 0)) { c->zs_state = deflate(&c->zs, Z_FINISH); /* when zlib claims to be done, add the suffix info */ if (c->zs_state == Z_STREAM_END) { uLong crc = crc32(0L, Z_NULL, 0); /* crc32 must not be converted into network byte order */ crc = crc32(crc, (Bytef *)c->hc->file_address, c->hc->sb.st_size); memcpy(c->zs.next_out, &crc, sizeof(uLong)); memcpy(c->zs.next_out + 4, &(hc->sb.st_size), 4); c->zs.next_out += 8; } } /* Do we need to write the headers first? */ iv_count = 1; iv[0].iov_base = c->zs_output_head; iv[0].iov_len = c->zs.next_out - (Bytef *)c->zs_output_head; if (hc->responselen != 0) { /* Yes. We'll combine headers and file into a single writev(), ** hoping that this generates a single packet. */ iv_count = 2; iv[0].iov_base = hc->response; iv[0].iov_len = hc->responselen; iv[1].iov_base = c->zs_output_head; iv[1].iov_len = c->zs.next_out - (Bytef *)c->zs_output_head; } sz = httpd_writev(hc, iv, iv_count); #endif /* HAVE_ZLIB_H */ } if (sz < 0 && errno == EINTR) { clear_connection(c, tv); return; } if (sz == 0 || (sz < 0 && (errno == EWOULDBLOCK || errno == EAGAIN))) { /* This shouldn't happen, but some kernels, e.g. ** SunOS 4.1.x, are broken and select() says that ** O_NDELAY sockets are always writable even when ** they're actually not. ** ** Current workaround is to block sending on this ** socket for a brief adaptively-tuned period. ** Fortunately we already have all the necessary ** blocking code, for use with throttling. */ c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; c->conn_state = CNST_PAUSING; fdwatch_del_fd(hc->conn_fd); arg.p = c; if (c->wakeup_timer) syslog(LOG_ERR, "replacing non-null wakeup_timer!"); c->wakeup_timer = tmr_create(tv, wakeup_connection, arg, c->wouldblock_delay, 0); if (!c->wakeup_timer) { syslog(LOG_CRIT, "tmr_create(wakeup_connection) failed"); exit(1); } return; } if (sz < 0) { /* Something went wrong, close this connection. ** ** If it's just an EPIPE, don't bother logging, that ** just means the client hung up on us. ** ** On some systems, write() occasionally gives an EINVAL. ** Dunno why, something to do with the socket going ** bad. Anyway, we don't log those either. ** ** And ECONNRESET isn't interesting either. */ if (errno != EPIPE && errno != EINVAL && errno != ECONNRESET && hc->errmsg) syslog(LOG_ERR, "write failed: %s while sending %s", hc->errmsg, hc->encodedurl); clear_connection(c, tv); return; } /* Ok, we wrote something. */ c->active_at = tv->tv_sec; /* Was this a headers + file writev()? */ if (hc->responselen > 0) { /* Yes; did we write only part of the headers? */ if ((size_t)sz < hc->responselen) { /* Yes; move the unwritten part to the front of the buffer. */ int newlen = hc->responselen - sz; memmove(hc->response, &(hc->response[sz]), newlen); hc->responselen = newlen; sz = 0; } else { /* Nope, we wrote the full headers, so adjust accordingly. */ sz -= hc->responselen; hc->responselen = 0; } } /* And update how much of the file we wrote. */ c->next_byte_index += sz; c->hc->bytes_sent += sz; for (tind = 0; tind < c->numtnums; ++tind) throttles[c->tnums[tind]].bytes_since_avg += sz; /* Are we done? */ if (c->hc->compression_type == COMPRESSION_NONE) { if (c->next_byte_index >= c->end_byte_index) { /* This connection is finished! */ finish_connection(c, tv); return; } #ifdef HAVE_ZLIB_H } else { if ((c->zs_state == Z_STREAM_END) && (c->zs_output_head + sz == c->zs.next_out)) { /* This conection is finished! */ clear_connection(c, tv); return; } else if (sz > 0) { /* move data to beginning of zlib output buffer ** and set up pointers so next zlib output goes ** to where we left off */ /* this can be optimized by using a looping buffer thing */ memcpy(c->zs_output_head, c->zs_output_head + sz, ZLIB_OUTPUT_BUF_SIZE - sz + 8); c->zs.next_out -= sz; c->zs.avail_out = sz; } #endif /* HAVE_ZLIB_H */ } /* Tune the (blockheaded) wouldblock delay. */ if (c->wouldblock_delay > MIN_WOULDBLOCK_DELAY) c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; /* If we're throttling, check if we're sending too fast. */ if (c->max_limit != THROTTLE_NOLIMIT) { elapsed = tv->tv_sec - c->started_at; if (elapsed == 0) elapsed = 1; /* count at least one second */ if (c->hc->bytes_sent / elapsed > c->max_limit) { c->conn_state = CNST_PAUSING; fdwatch_del_fd(hc->conn_fd); /* How long should we wait to get back on schedule? If less ** than a second (integer math rounding), use 1/2 second. */ coast = c->hc->bytes_sent / c->max_limit - elapsed; arg.p = c; if (c->wakeup_timer) syslog(LOG_ERR, "replacing non-null wakeup_timer!"); c->wakeup_timer = tmr_create(tv, wakeup_connection, arg, coast > 0 ? (coast * 1000L) : 500L, 0); if (!c->wakeup_timer) { syslog(LOG_CRIT, "tmr_create(wakeup_connection) failed"); exit(1); } } } /* (No check on min_limit here, that only controls connection startups.) */ } static void handle_linger(connecttab *c, struct timeval *tv) { ssize_t r; char buf[512]; /* In lingering-close mode we just read and ignore bytes. An error ** or EOF ends things, otherwise we go until a timeout. */ do { r = httpd_read(c->hc, buf, sizeof(buf)); if (r < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) return; } while (r > 0); if (r <= 0) really_clear_connection(c, tv); } static void idle(arg_t arg, struct timeval *now) { int cnum; connecttab *c; for (cnum = 0; cnum < max_connects; ++cnum) { c = &connects[cnum]; switch (c->conn_state) { case CNST_READING: if (now->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT) { syslog(LOG_INFO, "%.80s: connection timed out reading", httpd_client(c->hc)); // httpd_send_err(c->hc, 408, httpd_err408title, "", httpd_err408form, ""); finish_connection(c, now); } break; case CNST_SENDING: case CNST_PAUSING: if (now->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT) { syslog(LOG_INFO, "%.80s: connection timed out sending", httpd_client(c->hc)); clear_connection(c, now); } break; } } } static void occasional(arg_t arg, struct timeval *now) { mmc_cleanup(now); tmr_cleanup(); watchdog_flag = 1; /* let the watchdog know that we are alive */ } #ifdef STATS_TIME static void show_stats(arg_t arg, struct timeval *now) { logstats(now); } #endif /* SIGTERM and SIGINT say to exit immediately. */ static void handle_term(int signo) { syslog(LOG_NOTICE, "Exiting due to signal %d, dropping %d connections.", signo, num_connects); shut_down(); closelog(); exit(0); } /* SIGCHLD - a chile process exitted, so we need to reap the zombie */ static void handle_chld(int signo) { struct httpd *server; const int oerrno = errno; pid_t pid; int status; /* Reap defunct children until there aren't any more. */ while (1) { #ifdef HAVE_WAITPID pid = waitpid((pid_t)-1, &status, WNOHANG); #else pid = wait3(&status, WNOHANG, NULL); #endif if ((int)pid == 0) /* none left */ break; if ((int)pid < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; /* ECHILD shouldn't happen with the WNOHANG option, ** but with some kernels it does anyway. Ignore it. */ if (errno != ECHILD) syslog(LOG_ERR, "child wait: %s", strerror(errno)); break; } /* Decrement CGI count. Ignore PID from any CGI children */ LIST_FOREACH(server, server_list) { if (!httpd_cgi_untrack(server, pid)) break; } } /* Restore previous errno. */ errno = oerrno; } /* SIGBUS is a workaround for Linux 2.4.x / NFS */ static void handle_bus(int sig) { const int oerrno = errno; /* Just set a flag that we got the signal. */ got_bus = 1; /* Restore previous errno. */ errno = oerrno; } /* SIGHUP says to re-open the log file. */ static void handle_hup(int signo) { const int oerrno = errno; /* Just set a flag that we got the signal. */ got_hup = 1; /* Restore previous errno. */ errno = oerrno; } /* SIGUSR1 says to toggle debug mode. */ static void handle_usr1(int signo) { if (!got_usr1) got_usr1 = 1; } /* SIGUSR2 says to generate the stats syslogs immediately. */ static void handle_usr2(int signo) { const int oerrno = errno; logstats(NULL); /* Restore previous errno. */ errno = oerrno; } /* SIGALRM is used as a watchdog. */ static void handle_alrm(int signo) { const int oerrno = errno; /* If nothing has been happening */ if (!watchdog_flag) { syslog(LOG_WARNING, "Got caught reading Vogon poetry ... aborting."); #ifdef HAVE_BACKTRACE stack_trace(); #endif /* Try changing dirs to someplace we can write. */ chdir("/tmp"); /* Dump core. */ abort(); } watchdog_flag = 0; /* Set up alarm again. */ alarm(OCCASIONAL_TIME * 3); /* Restore previous errno. */ errno = oerrno; } static void init_signals(void) { size_t i; struct sigaction sa; struct { int signo; void (*cb)(int); } signals[] = { { SIGTERM, handle_term }, { SIGTERM, handle_term }, { SIGINT, handle_term }, { SIGCHLD, handle_chld }, { SIGPIPE, SIG_IGN }, /* get EPIPE instead */ { SIGHUP, handle_bus }, { SIGHUP, handle_hup }, { SIGUSR1, handle_usr1 }, { SIGUSR2, handle_usr2 }, { SIGALRM, handle_alrm }, }; memset(&sa, 0, sizeof(sa)); for (i = 0; i < NELEMS(signals); i++) { sa.sa_flags = SA_RESTART; sa.sa_handler = signals[i].cb; if (sigaction(signals[i].signo, &sa, NULL)) syslog(LOG_WARNING, "Failed setting up handler for signo %d: %s", signals[i].signo, strerror(errno)); } got_bus = 0; got_hup = 0; got_usr1 = 0; watchdog_flag = 0; alarm(OCCASIONAL_TIME * 3); } static int loglvl(char *level) { for (int i = 0; prioritynames[i].c_name; i++) { if (!strcmp(prioritynames[i].c_name, level)) return prioritynames[i].c_val; } return atoi(level); } static int usage(int code) { #ifdef HAVE_LIBCONFUSE printf("Usage: %s [OPTIONS] [WEBROOT]\n" "\n" " -f FILE Configuration file, default: " CONFDIR "/%s.conf\n" #else printf("Usage: %s [OPTIONS] [WEBROOT] [HOSTNAME]\n" "\n" " -c CGI CGI pattern to allow, e.g. \"**\", \"**.cgi\", \"/cgi-bin/*\",\n" " built-in default: \"" CGI_PATTERN "\"\n" " -d DIR Optional DIR to change into after chrooting to WEBROOT\n" " -g Use global password, .htpasswd, and .htaccess files\n" #endif " -h This help text\n" " -I IDENT Identity for syslog, .conf, and PID file, default: %s\n" " -l LEVEL Set log level: none, err, warning, notice*, info, debug\n" " -n Run in foreground, do not detach from controlling terminal\n" " -p PORT Port to listen to, default 80, or 443 if HTTPS is enabled\n" " -P PIDFN Path to PID file, default: " RUNDIR "/%s.pid\n" #ifndef HAVE_LIBCONFUSE " -r Chroot into WEBROOT\n" " -S Check symlinks so they don't point outside WEBROOT\n" #endif " -s Log to syslog, even though running in foreground, -n\n" " -t FILE Throttle file\n" #ifndef HAVE_LIBCONFUSE " -u USER Username to drop to, default: nobody\n" " -v Enable virtual hosting with WEBROOT as base\n" #endif " -V Show Merecat httpd version\n" "\n", prognm, #ifdef HAVE_LIBCONFUSE ident, #endif prognm, ident); printf( #ifndef HAVE_LIBCONFUSE "WEBROOT defaults to the current directory. HOSTNAME is for virtual\n" "hosting, one httpd per hostname. Note, '-d DIR' is not required in\n" "virtual hosting mode, see merecat(8) for details.\n" "\n" #endif "*) Default log level\n" "\n" "Bug report address: %-40s\n", PACKAGE_BUGREPORT); return code; } static int version(void) { printf("%s\n", PACKAGE_VERSION); return 0; } static char *progname(char *arg0) { char *nm; nm = strrchr(arg0, '/'); if (nm) nm++; else nm = arg0; return nm; } int main(int argc, char **argv) { struct http_conn *hc; struct httpd *server; struct timeval tv; struct passwd *pwd; connecttab *ct; uid_t uid = 32767; gid_t gid = 32767; char *pidfn = NULL; char *config = NULL; int log_opts = LOG_PID | LOG_NDELAY; int background = 1; int do_syslog = 1; int num_ready; int num, cnum; int c; ident = prognm = progname(argv[0]); while ((c = getopt(argc, argv, "c:d:f:ghI:l:np:P:rsSu:vV")) != EOF) { switch (c) { #ifndef HAVE_LIBCONFUSE case 'c': cgi_pattern = optarg; break; case 'd': data_dir = optarg; break; case 'g': do_global_passwd = 1; break; #endif case 'f': #ifndef HAVE_LIBCONFUSE syslog(LOG_ERR, "%s is not built with .conf file support", PACKAGE_NAME); return 1; #else config = optarg; #endif break; case 'h': return usage(0); case 'I': ident = optarg; break; case 'l': loglevel = loglvl(optarg); if (-1 == loglevel) return usage(1); if (LOG_DEBUG == loglevel) dbglevel = LOG_NOTICE; break; case 'n': background = 0; do_syslog--; break; case 'p': port = (unsigned short)atoi(optarg); break; case 'P': pidfn = optarg; break; case 's': do_syslog++; break; #ifndef HAVE_LIBCONFUSE case 'r': do_chroot = 1; no_symlink_check = 1; break; case 'S': no_symlink_check = 0; break; #endif case 't': throttlefile = optarg; break; #ifndef HAVE_LIBCONFUSE case 'u': user = optarg; break; case 'v': do_vhost = 1; break; #endif case 'V': return version(); default: return usage(1); } } if (optind < argc) dir = argv[optind++]; if (optind < argc) hostname = argv[optind++]; #ifdef LOG_PERROR if (!background && !do_syslog) log_opts |= LOG_PERROR; #endif openlog(ident, log_opts, LOG_FACILITY); setlogmask(LOG_UPTO(loglevel)); /* Read merecat.conf, if available */ conf_init(config); /* Read zone info now, in case we chroot(). */ tzset(); /* Throttle file. */ numthrottles = 0; maxthrottles = 0; throttles = NULL; if (throttlefile) read_throttlefile(throttlefile); /* If we're root and we're going to drop privileges to become another ** user, get their uid/gid now. */ if (getuid() == 0) { pwd = getpwnam(user); if (!pwd) { syslog(LOG_CRIT, "Unknown user - '%s'", user); exit(1); } uid = pwd->pw_uid; gid = pwd->pw_gid; } /* Switch directories if requested. */ if (dir) { if (chdir(dir) < 0) { syslog(LOG_CRIT, "chdir: %s", strerror(errno)); exit(1); } } #ifdef USE_USER_DIR else if (getuid() == 0) { /* No explicit directory was specified, we're root, and the ** USE_USER_DIR option is set - switch to the specified user's ** home dir. */ if (chdir(pwd->pw_dir) < 0) { syslog(LOG_CRIT, "chdir %s: %s", pwd->pw_dir, strerror(errno)); exit(1); } } #endif /* USE_USER_DIR */ /* Get current directory. */ getcwd(path, sizeof(path) - 1); if (path[strlen(path) - 1] != '/') strlcat(path, "/", sizeof(path)); if (background) { /* We're not going to use stdin stdout or stderr from here on, ** so close them to save file descriptors. */ (void)fclose(stdin); (void)fclose(stdout); (void)fclose(stderr); /* Daemonize - make ourselves a subprocess. */ #ifdef HAVE_DAEMON if (daemon(1, 1) < 0) { syslog(LOG_CRIT, "daemon: %s", strerror(errno)); exit(1); } #else /* HAVE_DAEMON */ switch (fork()) { case 0: break; case -1: syslog(LOG_CRIT, "fork: %s", strerror(errno)); exit(1); default: exit(0); } #ifdef HAVE_SETSID setsid(); #endif #endif /* HAVE_DAEMON */ } else { /* Even if we don't daemonize, we still want to disown our ** parent process. */ #ifdef HAVE_SETSID setsid(); #endif } /* Initialize the fdwatch package. We have to do this before ** chrooting, if /dev/poll is used. */ max_connects = fdwatch_get_nfiles(); if (max_connects < 0) { syslog(LOG_CRIT, "fdwatch initialization failure"); exit(1); } max_connects -= SPARE_FDS; /* Chroot if requested. */ if (do_chroot) { if (chroot(path) < 0) { syslog(LOG_CRIT, "chroot: %s", strerror(errno)); exit(1); } strlcpy(path, "/", sizeof(path)); /* Always chdir to / after a chroot. */ if (chdir(path) < 0) { syslog(LOG_CRIT, "chroot chdir: %s", strerror(errno)); exit(1); } } /* Switch directories again if requested. */ if (data_dir) { if (data_dir[0] == '/') strlcat(path, &data_dir[1], sizeof(path)); else strlcat(path, data_dir, sizeof(path)); if (chdir(path) < 0) { syslog(LOG_CRIT, "data-directory chdir: %s", strerror(errno)); exit(1); } if (path[strlen(path) - 1] != '/') strlcat(path, "/", sizeof(path)); } /* Set up to catch signals. */ init_signals(); /* Initialize the timer package. */ tmr_init(); /* Set up the occasional timer. */ if (!tmr_create(NULL, occasional, noarg, OCCASIONAL_TIME * 1000L, 1)) { syslog(LOG_CRIT, "tmr_create(occasional) failed"); exit(1); } /* Set up the idle timer. */ if (!tmr_create(NULL, idle, noarg, 5 * 1000L, 1)) { syslog(LOG_CRIT, "tmr_create(idle) failed"); exit(1); } if (numthrottles > 0) { /* Set up the throttles timer. */ if (!tmr_create(NULL, update_throttles, noarg, THROTTLE_TIME * 1000L, 1)) { syslog(LOG_CRIT, "tmr_create(update_throttles) failed"); exit(1); } } #ifdef STATS_TIME /* Set up the stats timer. */ if (!tmr_create(NULL, show_stats, noarg, STATS_TIME * 1000L, 1)) { syslog(LOG_CRIT, "tmr_create(show_stats) failed"); exit(1); } #endif start_time = stats_time = time(NULL); stats_connections = 0; stats_bytes = 0; stats_simultaneous = 0; /* Initialize our connections table. */ connects = NEW(connecttab, max_connects); if (!connects) { syslog(LOG_CRIT, "Out of memory allocating a connecttab"); exit(1); } for (cnum = 0; cnum < max_connects; ++cnum) { connects[cnum].conn_state = CNST_FREE; connects[cnum].next_free_connect = cnum + 1; connects[cnum].hc = NULL; #ifdef HAVE_ZLIB_H connects[cnum].zs_output_head = NULL; #endif } connects[max_connects - 1].next_free_connect = -1; /* end of link list */ first_free_connect = 0; num_connects = 0; httpd_conn_count = 0; /* Create PID file */ if (!pidfn) pidfn = ident; pidfile(pidfn); /* Get servers from .conf file */ num = conf_srv(srvtab, NELEMS(srvtab)); if (num == -1) { syslog(LOG_CRIT, "No server{} directive in .conf file and no valid global settings ..."); exit(1); } for (int i = 0; i < num; i++) { /* Create the server */ server = srv_init(&srvtab[i]); /* Add to list of servers */ if (server) LIST_INSERT(server, server_list); } /* Start socket watchers for all servers */ LIST_FOREACH(server, server_list) srv_start(server); /* If we're root, try to become someone else. */ if (getuid() == 0) { /* Set aux groups to null. */ if (setgroups(0, NULL) < 0) { syslog(LOG_CRIT, "setgroups: %s", strerror(errno)); exit(1); } /* Set primary group. */ if (setgid(gid) < 0) { syslog(LOG_CRIT, "setgid: %s", strerror(errno)); exit(1); } /* Try setting aux groups correctly - not critical if this fails. */ if (initgroups(user, gid) < 0) syslog(LOG_WARNING, "initgroups: %s", strerror(errno)); #ifdef HAVE_SETLOGIN /* Set login name. */ setlogin(user); #endif /* Set uid. */ if (setuid(uid) < 0) { syslog(LOG_CRIT, "setuid: %s", strerror(errno)); exit(1); } /* Check for unnecessary security exposure. */ if (!do_chroot) syslog(LOG_WARNING, "Started as root without requesting chroot(), warning only"); } /* Main loop. */ tmr_prepare_timeval(&tv); while ((!terminate) || num_connects > 0) { int got = 0; /* Do we need to re-open the log file? */ if (got_hup) got_hup = 0; /* Do the fd watch. */ num_ready = fdwatch(tmr_mstimeout(&tv)); if (num_ready < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; /* try again */ syslog(LOG_ERR, "fdwatch: %s", strerror(errno)); exit(1); } tmr_prepare_timeval(&tv); if (num_ready == 0) { /* No fd's are ready - run the timers. */ tmr_run(&tv); continue; } LIST_FOREACH(server, server_list) { if ((got = srv_connect(server, &tv))) break; } /* Go around the loop and do another fdwatch, ** rather than dropping through and processing ** existing connections. New connections ** always get priority. */ if (got) continue; /* Find the connections that need servicing. */ while ((ct = (connecttab *)fdwatch_get_next_arg()) != (connecttab *)-1) { if (!ct) continue; hc = ct->hc; if (!fdwatch_check_fd(hc->conn_fd)) { /* Something went wrong. */ hc->do_keep_alive = 0; clear_connection(ct, &tv); } else { switch (ct->conn_state) { case CNST_READING: handle_read(ct, &tv); break; case CNST_SENDING: handle_send(ct, &tv); break; case CNST_LINGERING: handle_linger(ct, &tv); break; } } } tmr_run(&tv); if (got_usr1) { loglevel = loglevel + dbglevel; dbglevel = loglevel - dbglevel; loglevel = loglevel - dbglevel; setlogmask(LOG_UPTO(loglevel)); got_usr1 = 0; } /* From handle_send()/writev; see handle_sigbus(). */ if (got_bus) { syslog(LOG_WARNING, "SIGBUS received - stale NFS-handle?"); got_bus = 0; } } /* The main loop terminated. */ shut_down(); syslog(LOG_NOTICE, "Exiting cleanly, all connections completed."); closelog(); exit(0); } merecat-2.31+git20220513+ds/src/merecat.h000066400000000000000000000431541424066547300174610ustar00rootroot00000000000000/* Configuration defines for merecat and libhttpd ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MERECAT_H_ #define MERECAT_H_ #include #include #include #include #include #include /* The following configuration settings are sorted in order of decreasing ** likelihood that you'd want to change them - most likely first, least ** likely last. */ /* CONFIGURE: Server software version, standard httpd version format. ** You may want to add a build ID here, but probably not. */ #define SERVER_SOFTWARE PACKAGE "/" VERSION /* CONFIGURE: CGI programs must match this pattern to get executed. It's ** a simple shell-style wildcard pattern, with * meaning any string not ** containing a slash, ** meaning any string at all, and ? meaning any ** single character; or multiple such patterns separated by |. The ** patterns get checked against the filename part of the incoming URL. ** ** Restricting CGI programs to a single directory lets the site administrator ** review them for security holes, and is strongly recommended. If there ** are individual users that you trust, you can enable their directories too. ** ** You can also specify a CGI pattern on the command line, with the -c flag. ** Such a pattern overrides this compiled-in default. ** ** If no CGI pattern is specified, neither here nor on the command line, ** then CGI programs cannot be run at all. If you want to disable CGI ** as a security measure that's how you do it, just don't define any ** pattern here and don't run with the -c flag. ** ** Some sample patterns follows: */ #if 0 /* Allow programs only in one central directory: */ #define CGI_PATTERN "/cgi-bin/*" /* Allow programs in a central directory, or anywhere in a trusted user's tree: */ #define CGI_PATTERN "/cgi-bin/*|/jef/**" /* Allow any program ending with a .cgi: */ #define CGI_PATTERN "**.cgi" /* When virtual hosting, enable the central directory on every host: */ #define CGI_PATTERN "/*/cgi-bin/*" #endif /* 0 */ /* Fallback, any program ending with a .cgi or any program in /cgi-bin/ */ #ifndef CGI_PATTERN #define CGI_PATTERN "**.cgi|/cgi-bin/*" #endif /* CONFIGURE: How many seconds to allow CGI programs to run before killing ** them. This is in case someone writes a CGI program that goes into an ** infinite loop, or does a massive database lookup that would take hours, ** or whatever. If you don't want any limit, comment this out, but that's ** probably a really bad idea. */ #define CGI_TIMELIMIT 90 /* CONFIGURE: Maximum number of simultaneous CGI programs allowed. ** If this many are already running, then attempts to run more will ** return an HTTP 503 error. If this is not defined then there's ** no limit (and you'd better have a lot of memory). This can also be ** set in the runtime config file. */ #define CGI_LIMIT 50 /* Fallback */ #ifndef CGI_LIMIT #define CGI_LIMIT 1 #endif /* CONFIGURE: How many seconds to allow for reading the initial request ** on a new connection. */ #define IDLE_READ_TIMELIMIT 60 /* CONFIGURE: How many seconds before an idle connection gets closed. */ #define IDLE_SEND_TIMELIMIT 300 /* CONFIGURE: The syslog facility to use. Using this you can set up your ** syslog.conf so that all merecat messages go into a separate file. Note ** that even if you use the -l command line flag to send logging to a ** file, errors still get sent via syslog. */ #define LOG_FACILITY LOG_DAEMON /* CONFIGURE: Tilde mapping. Many URLs use ~username to indicate a ** user's home directory. merecat provides two options for mapping ** this construct to an actual filename. ** ** 1) Map ~username to /username. This is the recommended choice. ** Each user gets a subdirectory in the main chrootable web tree, and ** the tilde construct points there. The prefix could be something ** like "users", or it could be empty. See also the makeweb program ** for letting users create their own web subdirectories. ** ** 2) Map ~username to /. The postfix would be ** the name of a subdirectory off of the user's actual home dir, something ** like "public_html". This is what Apache and other servers do. The problem ** is, you can't do this and chroot() at the same time, so it's inherently ** a security hole. This is strongly dis-recommended, but it's here because ** some people really want it. Use at your own risk. ** ** You can also leave both options undefined, and merecat will not do ** anything special about tildes. Enabling both options is an error. */ #ifdef notdef #define TILDE_MAP_1 "users" #define TILDE_MAP_2 "public_html" #endif /* CONFIGURE: The file to use for authentication. If this is defined then ** merecat checks for this file in the local directory before every fetch. ** If the file exists then authentication is done, otherwise the fetch ** proceeds as usual. ** ** If you undefine this then merecat will not implement authentication ** at all and will not check for auth files, which saves a bit of CPU time. */ #ifdef notdef #define AUTH_FILE ".htpasswd" #endif /* CONFIGURE: The file to use for restricting access on an ip basis. If ** this is defined then merecat checks for this file in the local ** directory before every fetch. If the file exists then merecat checks ** whether the client's ip address is allowed to fetch this file, otherwise ** the fetch is denied. ** ** If you undefine this then merecat will not implement access checks ** at all and will not check for access files, which saves a bit of CPU time. */ #ifdef notdef #define ACCESS_FILE ".htaccess" #endif /* CONFIGURE: The default character set name to use with text MIME types. ** This gets substituted into the MIME types where they have a "%s". ** ** You can override this in the config file with the "charset" setting, ** or on the command like with the -T flag. */ #define DEFAULT_CHARSET "UTF-8" /* Most people won't want to change anything below here. */ /* CONFIGURE: This controls the SERVER_NAME environment variable that gets ** passed to CGI programs. By default merecat does a gethostname(), which ** gives the host's canonical name. If you want to always use some other name ** you can define it here. ** ** Alternately, if you want to run the same merecat binary on multiple ** machines, and want to build in alternate names for some or all of ** them, you can define a list of canonical name to altername name ** mappings. merecat seatches the list and when it finds a match on ** the canonical name, that alternate name gets used. If no match ** is found, the canonical name gets used. ** ** If both SERVER_NAME and SERVER_NAME_LIST are defined here, merecat searches ** the list as above, and if no match is found then SERVER_NAME gets used. ** ** In any case, if merecat is started with the -h flag, that name always ** gets used. */ #ifdef notdef #define SERVER_NAME "your.hostname.here" #define SERVER_NAME_LIST \ "canonical.name.here/alternate.name.here", \ "canonical.name.two/alternate.name.two" #endif /* CONFIGURE: Undefine this if you want merecat to hide its specific version ** when returning into to browsers. Instead it'll just say "merecat" with ** no version. */ #define SHOW_SERVER_VERSION /* CONFIGURE: If you're using the vhost feature and you have a LOT of ** virtual hostnames (like, hundreds or thousands), you will want to ** enable this feature. It avoids a problem with most Unix filesystems, ** where if there are a whole lot of items in a directory then name lookup ** becomes very slow. This feature makes merecat use subdirectories ** based on the first characters of each hostname. You can set it to use ** from one to three characters. If the hostname starts with "www.", that ** part is skipped over. Dots are also skipped over, and if the name isn't ** long enough then "_"s are used. Here are some examples of how hostnames ** would get turned into directory paths, for each different setting: ** 1: www.acme.com -> a/www.acme.com ** 1: foobar.acme.com -> f/foobar.acme.com ** 2: www.acme.com -> a/c/www.acme.com ** 2: foobar.acme.com -> f/o/foobar.acme.com ** 3: www.acme.com -> a/c/m/www.acme.com ** 3: foobar.acme.com -> f/o/o/foobar.acme.com ** 3: m.tv -> m/t/v/m.tv ** 4: m.tv -> m/t/v/_/m.tv ** Note that if you compile this setting in but then forget to set up ** the corresponding subdirectories, the only error indication you'll ** get is a "404 Not Found" when you try to visit a site. So be careful. */ #ifdef notdef #define VHOST_DIRLEVELS 1 #define VHOST_DIRLEVELS 2 #define VHOST_DIRLEVELS 3 #endif /* CONFIGURE: When started as root, the default username to switch to after ** initializing. If this user (or the one specified by the -u flag) does ** not exist, the program will refuse to run. */ #define DEFAULT_USER "nobody" /* CONFIGURE: When started as root, the program can automatically chdir() ** to the home directory of the user specified by -u or DEFAULT_USER. ** An explicit -d still overrides this. */ #ifdef notdef #define USE_USER_DIR #endif /* CONFIGURE: If this is defined, some of the built-in error pages will ** have more explicit information about exactly what the problem is. ** Some sysadmins don't like this, for security reasons. */ #define EXPLICIT_ERROR_PAGES /* CONFIGURE: Subdirectory for custom error pages. The error filenames are ** $WEBDIR/$ERR_DIR/err%d.html - if virtual hosting is enabled then ** $WEBDIR/hostname/$ERR_DIR/err%d.html is searched first. This allows ** different custom error pages for each virtual hosting web server. If ** no custom page for a given error can be found, the built-in error page ** is generated. If ERR_DIR is not defined at all, only the built-in error ** pages will be generated. */ #define ERR_DIR "errors" /* CONFIGURE: Define this if you want a standard HTML tail containing ** $SERVER_SOFTWARE, hostname, and port to be appended to the custom ** error pages. (It is always appended to the built-in error pages.) */ #define ERR_APPEND_SERVER_INFO /* CONFIGURE: nice(2) value to use for CGI programs. If this is undefined, ** CGI programs run at normal priority. */ #define CGI_NICE 10 /* CONFIGURE: $PATH to use for CGI programs. */ #define CGI_PATH "/usr/local/bin:/bin:/usr/bin:/usr/lib/cgi-bin" /* CONFIGURE: If defined, $LD_LIBRARY_PATH to use for CGI programs. */ #ifdef notdef #define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib:/lib" #endif /* CONFIGURE: How often to run the occasional cleanup job. */ #define OCCASIONAL_TIME 120 /* CONFIGURE: Seconds between stats syslogs. If this is undefined then ** no stats are accumulated and no stats syslogs are done. ** Original default: 3600 */ #undef STATS_TIME /* CONFIGURE: The mmap cache tries to keep the total number of mapped ** files below this number, so you don't run out of kernel file descriptors. ** If you have reconfigured your kernel to have more descriptors, you can ** raise this and merecat will keep more maps cached. However it's not ** a hard limit, merecat will go over it if you really are accessing ** a whole lot of files. */ #define DESIRED_MAX_MAPPED_FILES 1000 /* CONFIGURE: The mmap cache also tries to keep the total mapped bytes ** below this number, so you don't run out of address space. Again ** it's not a hard limit, merecat will go over it if you really are ** accessing a bunch of large files. */ #define DESIRED_MAX_MAPPED_BYTES 1000000000 /* CONFIGURE: Minimum and maximum intervals between child-process reaping, ** in seconds. */ #define MIN_REAP_TIME 90 #define MAX_REAP_TIME 900 /* You almost certainly don't want to change anything below here. */ /* CONFIGURE: When throttling CGI programs, we don't know how many bytes ** they send back to the client because it would be inefficient to ** interpose a counter. CGI programs are much more expensive than ** regular files to serve, so we set an arbitrary and high byte count ** that gets applied to all CGI programs for throttling purposes. */ #define CGI_BYTECOUNT 25000 /* CONFIGURE: The default port to listen on. 80 is the standard HTTP * port and 443 the standard HTTPS port. */ #define DEFAULT_HTTP_PORT 80 #define DEFAULT_HTTPS_PORT 443 /* CONFIGURE: A list of index filenames to check. The files are searched ** for in this order. */ #define INDEX_NAMES "index.shtml", "index.shtm", "index.stm", \ "index.cgi", "index.php", \ "index.html", "index.htm", \ "index.xhtml", "index.xht", \ "Default.htm" /* CONFIGURE: If this is defined then merecat will automatically generate ** index pages for directories that don't have an explicit index file. ** If you want to disable this behavior site-wide, perhaps for security ** reasons, just undefine this. Note that you can disable indexing of ** individual directories by merely doing a "chmod 711" on them - the ** standard Unix file permission to allow file access but disable "ls". */ #ifdef notdef #define GENERATE_INDEXES #endif /* CONFIGURE: Whether to log unknown request headers. Most sites will not ** want to log them, which will save them a bit of CPU time. */ #ifdef notdef #define LOG_UNKNOWN_HEADERS #endif /* CONFIGURE: Time between updates of the throttle table's rolling averages. */ #define THROTTLE_TIME 2 /* CONFIGURE: The listen() backlog queue length. The 1024 doesn't actually ** get used, the kernel uses its maximum allowed value. This is a config ** parameter only in case there's some OS where asking for too high a queue ** length causes an error. Note that on many systems the maximum length is ** way too small - see http://www.acme.com/software/thttpd/notes.html */ #define LISTEN_BACKLOG 1024 /* CONFIGURE: Maximum number of throttle patterns that any single URL can ** be included in. This has nothing to do with the number of throttle ** patterns that you can define, which is unlimited. */ #define MAXTHROTTLENUMS 10 /* CONFIGURE: Number of file descriptors to reserve for uses other than ** connections. Currently this is 10, representing one for the listen fd, ** one for dup()ing at connection startup time, one for reading the file, ** one for syslog, and possibly one for the regular log file, which is ** five, plus a factor of two for who knows what. */ #define SPARE_FDS 10 /* CONFIGURE: How many milliseconds to leave a connection open while doing a ** lingering close. */ #define LINGER_TIME 500 /* CONFIGURE: How many milliseconds to keep a keep-alive connection ** alive for pipelining clients before turning it into a lingering ** connection. */ #define KEEPALIVE_TIMELIMIT (1 * 1000L) /* CONFIGURE: Maximum number of symbolic links to follow before ** assuming there's a loop. */ #define MAX_LINKS 32 /* ** CONFIGURE: You don't even want to know. */ #define MIN_WOULDBLOCK_DELAY 100L /* ** Default SSL/TLS protocol and cipher suites */ #define SSL_DEFAULT_PROTO "TLSv1.1" #define SSL_DEFAULT_CIPHERS "TLS_AES_256_GCM_SHA384:" \ "TLS_CHACHA20_POLY1305_SHA256:" \ "TLS_AES_128_GCM_SHA256:" \ "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4:" \ "!DHE-RSA-CAMELLIA256-SHA:!DHE-RSA-CAMELLIA128-SHA:" \ "!ECDHE-RSA-CHACHA20-POLY1305:!DHE-RSA-CHACHA20-POLY1305:" \ "!DHE-RSA-AES256-CCM8:!DHE-RSA-AES256-CCM:" \ "!DHE-RSA-AES128-CCM8:!DHE-RSA-AES128-CCM" /* ** The minimum age for strict TLS should be > 180 days */ #define HSTS_MIN_AGE (180 * 24 * 60 * 60) /* ** Global variables */ extern char *prognm; extern char *ident; extern int loglevel; extern char path[]; /* ** Global config settings */ extern uint16_t port; extern int max_age; extern int compression_level; extern int do_chroot; extern int do_vhost; extern int do_global_passwd; extern int do_list_dotfiles; extern int no_symlink_check; extern int no_empty_referers; extern int cgi_enabled; extern int cgi_limit; extern char *cgi_pattern; extern char *local_pattern; extern char *php_cgi; extern char *php_pattern; extern char *ssi_cgi; extern int ssi_silent; extern char *ssi_pattern; extern char *url_pattern; extern char *dir; extern char *data_dir; extern char *hostname; extern char *user; extern char *charset; extern char *useragent_deny; /* Replacement functions for often missing APIs */ #ifndef strlcpy size_t strlcpy (char *dst, const char *src, size_t len); #endif #ifndef strlcat size_t strlcat (char *dst, const char *src, size_t len); #endif /* Vogon poetry generator */ #ifndef HAVE_BACKTRACE #define stacktrace() #else void stack_trace(void); #endif #endif /* MERECAT_H_ */ merecat-2.31+git20220513+ds/src/mime_encodings.h000066400000000000000000000002331424066547300210100ustar00rootroot00000000000000{ "gz", 0, "gzip", 0 }, { "xz", 0, "xz", 0 }, { "bz2", 0, "bzip2", 0 }, { "svgz", 0, "gzip", 0 }, { "Z", 0, "compress", 0 }, { "uu", 0, "x-uuencode", 0 }, merecat-2.31+git20220513+ds/src/mime_encodings.txt000066400000000000000000000003411424066547300214000ustar00rootroot00000000000000# mime_encodings.txt # # A list of file extensions followed by the corresponding MIME encoding. # Extensions not found in the table proceed to the mime_types table. gz gzip xz xz bz2 bzip2 svgz gzip Z compress uu x-uuencode merecat-2.31+git20220513+ds/src/mime_types.h000066400000000000000000000214251424066547300202110ustar00rootroot00000000000000{ "7z", 0, "application/x-7z-compressed", 0 }, { "a", 0, "application/octet-stream", 0 }, { "aab", 0, "application/x-authorware-bin", 0 }, { "aam", 0, "application/x-authorware-map", 0 }, { "aas", 0, "application/x-authorware-seg", 0 }, { "ai", 0, "application/postscript", 0 }, { "aif", 0, "audio/x-aiff", 0 }, { "aifc", 0, "audio/x-aiff", 0 }, { "aiff", 0, "audio/x-aiff", 0 }, { "arc", 0, "application/octet-stream", 0 }, { "arj", 0, "application/octet-stream", 0 }, { "asc", 0, "text/plain; charset=%s", 0 }, { "asf", 0, "video/x-ms-asf", 0 }, { "asx", 0, "video/x-ms-asf", 0 }, { "au", 0, "audio/basic", 0 }, { "avi", 0, "video/x-msvideo", 0 }, { "avif", 0, "image/avif", 0 }, { "bcpio", 0, "application/x-bcpio", 0 }, { "bin", 0, "application/octet-stream", 0 }, { "bmp", 0, "image/bmp", 0 }, { "bz2", 0, "application/x-bzip2", 0 }, { "cdf", 0, "application/x-netcdf", 0 }, { "class", 0, "application/x-java-vm", 0 }, { "cpio", 0, "application/x-cpio", 0 }, { "cpt", 0, "application/mac-compactpro", 0 }, { "crl", 0, "application/x-pkcs7-crl", 0 }, { "cer", 0, "application/x-x509-ca-cert", 0 }, { "crt", 0, "application/x-x509-ca-cert", 0 }, { "csh", 0, "application/x-csh", 0 }, { "css", 0, "text/css; charset=%s", 0 }, { "dcr", 0, "application/x-director", 0 }, { "der", 0, "application/x-x509-ca-cert", 0 }, { "dir", 0, "application/x-director", 0 }, { "disco", 0, "text/xml", 0 }, { "djv", 0, "image/vnd.djvu", 0 }, { "djvu", 0, "image/vnd.djvu", 0 }, { "dll", 0, "application/octet-stream", 0 }, { "dms", 0, "application/octet-stream", 0 }, { "doc", 0, "application/msword", 0 }, { "dtd", 0, "text/xml; charset=%s", 0 }, { "dump", 0, "application/octet-stream", 0 }, { "dvi", 0, "application/x-dvi", 0 }, { "dxr", 0, "application/x-director", 0 }, { "eps", 0, "application/postscript", 0 }, { "etx", 0, "text/x-setext", 0 }, { "exe", 0, "application/octet-stream", 0 }, { "ez", 0, "application/andrew-inset", 0 }, { "fgd", 0, "application/x-director", 0 }, { "fh", 0, "image/x-freehand", 0 }, { "fh4", 0, "image/x-freehand", 0 }, { "fh5", 0, "image/x-freehand", 0 }, { "fh7", 0, "image/x-freehand", 0 }, { "fhc", 0, "image/x-freehand", 0 }, { "gif", 0, "image/gif", 0 }, { "gtar", 0, "application/x-gtar", 0 }, { "gz", 0, "application/gzip", 0 }, { "hdf", 0, "application/x-hdf", 0 }, { "hqx", 0, "application/mac-binhex40", 0 }, { "htm", 0, "text/html; charset=%s", 0 }, { "html", 0, "text/html; charset=%s", 0 }, { "ice", 0, "x-conference/x-cooltalk", 0 }, { "ico", 0, "image/x-icon", 0 }, { "ief", 0, "image/ief", 0 }, { "iges", 0, "model/iges", 0 }, { "igs", 0, "model/iges", 0 }, { "iv", 0, "application/x-inventor", 0 }, { "jar", 0, "application/x-java-archive", 0 }, { "jfif", 0, "image/jpeg", 0 }, { "jpe", 0, "image/jpeg", 0 }, { "jpeg", 0, "image/jpeg", 0 }, { "jpg", 0, "image/jpeg", 0 }, { "js", 0, "application/javascript", 0 }, { "kar", 0, "audio/midi", 0 }, { "kml", 0, "application/vnd.google-earth.kml+xml", 0 }, { "kmz", 0, "application/vnd.google-earth.kmz", 0 }, { "latex", 0, "application/x-latex", 0 }, { "lha", 0, "application/octet-stream", 0 }, { "loc", 0, "application/xml-loc", 0 }, { "lzh", 0, "application/octet-stream", 0 }, { "m3u", 0, "audio/x-mpegurl", 0 }, { "man", 0, "application/x-troff-man", 0 }, { "mathml", 0, "application/mathml+xml", 0 }, { "me", 0, "application/x-troff-me", 0 }, { "mesh", 0, "model/mesh", 0 }, { "mid", 0, "audio/midi", 0 }, { "midi", 0, "audio/midi", 0 }, { "mif", 0, "application/vnd.mif", 0 }, { "mime", 0, "message/rfc822", 0 }, { "mml", 0, "application/mathml+xml", 0 }, { "mov", 0, "video/quicktime", 0 }, { "movie", 0, "video/x-sgi-movie", 0 }, { "mp2", 0, "audio/mpeg", 0 }, { "mp3", 0, "audio/mpeg", 0 }, { "mp4", 0, "video/mp4", 0 }, { "mpe", 0, "video/mpeg", 0 }, { "mpeg", 0, "video/mpeg", 0 }, { "mpg", 0, "video/mpeg", 0 }, { "mpga", 0, "audio/mpeg", 0 }, { "ms", 0, "application/x-troff-ms", 0 }, { "msh", 0, "model/mesh", 0 }, { "mv", 0, "video/x-sgi-movie", 0 }, { "mxu", 0, "video/vnd.mpegurl", 0 }, { "nc", 0, "application/x-netcdf", 0 }, { "o", 0, "application/octet-stream", 0 }, { "oda", 0, "application/oda", 0 }, { "ogg", 0, "application/ogg", 0 }, { "ogv", 0, "video/ogg", 0 }, { "ogx", 0, "application/ogg", 0 }, { "pac", 0, "application/x-ns-proxy-autoconfig", 0 }, { "pbm", 0, "image/x-portable-bitmap", 0 }, { "pdb", 0, "chemical/x-pdb", 0 }, { "pdf", 0, "application/pdf", 0 }, { "pgm", 0, "image/x-portable-graymap", 0 }, { "pgn", 0, "application/x-chess-pgn", 0 }, { "png", 0, "image/png", 0 }, { "pnm", 0, "image/x-portable-anymap", 0 }, { "ppm", 0, "image/x-portable-pixmap", 0 }, { "ppt", 0, "application/vnd.ms-powerpoint", 0 }, { "ps", 0, "application/postscript", 0 }, { "qt", 0, "video/quicktime", 0 }, { "ra", 0, "audio/x-realaudio", 0 }, { "ram", 0, "audio/x-pn-realaudio", 0 }, { "ras", 0, "image/x-cmu-raster", 0 }, { "rdf", 0, "application/rdf+xml", 0 }, { "rgb", 0, "image/x-rgb", 0 }, { "rm", 0, "audio/x-pn-realaudio", 0 }, { "roff", 0, "application/x-troff", 0 }, { "rpm", 0, "audio/x-pn-realaudio-plugin", 0 }, { "rss", 0, "application/rss+xml", 0 }, { "rtf", 0, "text/rtf; charset=%s", 0 }, { "rtx", 0, "text/richtext; charset=%s", 0 }, { "sfx", 0, "application/octet-stream", 0 }, { "sgm", 0, "text/sgml; charset=%s", 0 }, { "sgml", 0, "text/sgml; charset=%s", 0 }, { "sh", 0, "application/x-sh", 0 }, { "shar", 0, "application/x-shar", 0 }, { "sig", 0, "application/pgp-signature", 0 }, { "silo", 0, "model/mesh", 0 }, { "sit", 0, "application/x-stuffit", 0 }, { "skd", 0, "application/x-koan", 0 }, { "skm", 0, "application/x-koan", 0 }, { "skp", 0, "application/x-koan", 0 }, { "skt", 0, "application/x-koan", 0 }, { "smi", 0, "application/smil", 0 }, { "smil", 0, "application/smil", 0 }, { "snd", 0, "audio/basic", 0 }, { "so", 0, "application/octet-stream", 0 }, { "spl", 0, "application/x-futuresplash", 0 }, { "src", 0, "application/x-wais-source", 0 }, { "stc", 0, "application/vnd.sun.xml.calc.template", 0 }, { "std", 0, "application/vnd.sun.xml.draw.template", 0 }, { "sti", 0, "application/vnd.sun.xml.impress.template", 0 }, { "stw", 0, "application/vnd.sun.xml.writer.template", 0 }, { "sv4cpio", 0, "application/x-sv4cpio", 0 }, { "sv4crc", 0, "application/x-sv4crc", 0 }, { "svg", 0, "image/svg+xml", 0 }, { "svgx", 0, "image/svg+xml", 0 }, { "svgz", 0, "image/svg+xml", 0 }, { "swf", 0, "application/x-shockwave-flash", 0 }, { "sxc", 0, "application/vnd.sun.xml.calc", 0 }, { "sxd", 0, "application/vnd.sun.xml.draw", 0 }, { "sxg", 0, "application/vnd.sun.xml.writer.global", 0 }, { "sxi", 0, "application/vnd.sun.xml.impress", 0 }, { "sxm", 0, "application/vnd.sun.xml.math", 0 }, { "sxw", 0, "application/vnd.sun.xml.writer", 0 }, { "t", 0, "application/x-troff", 0 }, { "tar", 0, "application/x-tar", 0 }, { "taz", 0, "application/octet-stream", 0 }, { "tgz", 0, "application/gzip", 0 }, { "tbz2", 0, "application/x-bzip2", 0 }, { "txz", 0, "application/x-xz", 0 }, { "tcl", 0, "application/x-tcl", 0 }, { "tex", 0, "application/x-tex", 0 }, { "texi", 0, "application/x-texinfo", 0 }, { "texinfo", 0, "application/x-texinfo", 0 }, { "tif", 0, "image/tiff", 0 }, { "tiff", 0, "image/tiff", 0 }, { "torrent", 0, "application/x-bittorrent", 0 }, { "tr", 0, "application/x-troff", 0 }, { "tsp", 0, "application/dsptype", 0 }, { "tsv", 0, "text/tab-separated-values; charset=%s", 0 }, { "txt", 0, "text/plain; charset=%s", 0 }, { "tz", 0, "application/octet-stream", 0 }, { "ustar", 0, "application/x-ustar", 0 }, { "vcd", 0, "application/x-cdlink", 0 }, { "vrml", 0, "model/vrml", 0 }, { "vx", 0, "video/x-rad-screenplay", 0 }, { "wav", 0, "audio/x-wav", 0 }, { "wax", 0, "audio/x-ms-wax", 0 }, { "wbmp", 0, "image/vnd.wap.wbmp", 0 }, { "wbxml", 0, "application/vnd.wap.wbxml", 0 }, { "webm", 0, "video/webm", 0 }, { "webp", 0, "image/webp", 0 }, { "wm", 0, "video/x-ms-wm", 0 }, { "wma", 0, "audio/x-ms-wma", 0 }, { "wmd", 0, "application/x-ms-wmd", 0 }, { "wml", 0, "text/vnd.wap.wml", 0 }, { "wmlc", 0, "application/vnd.wap.wmlc", 0 }, { "wmls", 0, "text/vnd.wap.wmlscript", 0 }, { "wmlsc", 0, "application/vnd.wap.wmlscriptc", 0 }, { "wmv", 0, "video/x-ms-wmv", 0 }, { "wmx", 0, "video/x-ms-wmx", 0 }, { "wmz", 0, "application/x-ms-wmz", 0 }, { "wrl", 0, "model/vrml", 0 }, { "wsrc", 0, "application/x-wais-source", 0 }, { "wvx", 0, "video/x-ms-wvx", 0 }, { "xbm", 0, "image/x-xbitmap", 0 }, { "xht", 0, "application/xhtml+xml; charset=%s", 0 }, { "xhtml", 0, "application/xhtml+xml; charset=%s", 0 }, { "xls", 0, "application/vnd.ms-excel", 0 }, { "xml", 0, "text/xml; charset=%s", 0 }, { "xpi", 0, "application/x-xpinstall", 0 }, { "xpm", 0, "image/x-xpixmap", 0 }, { "xsd", 0, "text/xml; charset=%s", 0 }, { "xsl", 0, "text/xml; charset=%s", 0 }, { "xslt", 0, "text/xml; charset=%s", 0 }, { "xul", 0, "application/vnd.mozilla.xul+xml", 0 }, { "xwd", 0, "image/x-xwindowdump", 0 }, { "xz", 0, "application/x-xz", 0 }, { "xyz", 0, "chemical/x-xyz", 0 }, { "zip", 0, "application/zip", 0 }, { "zoo", 0, "application/octet-stream", 0 }, merecat-2.31+git20220513+ds/src/mime_types.txt000066400000000000000000000127161424066547300206040ustar00rootroot00000000000000# mime_types.txt # # A list of file extensions followed by the corresponding MIME type. # Extensions not found in the table are returned as text/plain. 7z application/x-7z-compressed a application/octet-stream aab application/x-authorware-bin aam application/x-authorware-map aas application/x-authorware-seg ai application/postscript aif audio/x-aiff aifc audio/x-aiff aiff audio/x-aiff arc application/octet-stream arj application/octet-stream asc text/plain; charset=%s asf video/x-ms-asf asx video/x-ms-asf au audio/basic avi video/x-msvideo avif image/avif bcpio application/x-bcpio bin application/octet-stream bmp image/bmp bz2 application/x-bzip2 cdf application/x-netcdf class application/x-java-vm cpio application/x-cpio cpt application/mac-compactpro crl application/x-pkcs7-crl cer application/x-x509-ca-cert crt application/x-x509-ca-cert csh application/x-csh css text/css; charset=%s dcr application/x-director der application/x-x509-ca-cert dir application/x-director disco text/xml djv image/vnd.djvu djvu image/vnd.djvu dll application/octet-stream dms application/octet-stream doc application/msword dtd text/xml; charset=%s dump application/octet-stream dvi application/x-dvi dxr application/x-director eps application/postscript etx text/x-setext exe application/octet-stream ez application/andrew-inset fgd application/x-director fh image/x-freehand fh4 image/x-freehand fh5 image/x-freehand fh7 image/x-freehand fhc image/x-freehand gif image/gif gtar application/x-gtar gz application/gzip hdf application/x-hdf hqx application/mac-binhex40 htm text/html; charset=%s html text/html; charset=%s ice x-conference/x-cooltalk ico image/x-icon ief image/ief iges model/iges igs model/iges iv application/x-inventor jar application/x-java-archive jfif image/jpeg jpe image/jpeg jpeg image/jpeg jpg image/jpeg js application/javascript kar audio/midi kml application/vnd.google-earth.kml+xml kmz application/vnd.google-earth.kmz latex application/x-latex lha application/octet-stream loc application/xml-loc lzh application/octet-stream m3u audio/x-mpegurl man application/x-troff-man mathml application/mathml+xml me application/x-troff-me mesh model/mesh mid audio/midi midi audio/midi mif application/vnd.mif mime message/rfc822 mml application/mathml+xml mov video/quicktime movie video/x-sgi-movie mp2 audio/mpeg mp3 audio/mpeg mp4 video/mp4 mpe video/mpeg mpeg video/mpeg mpg video/mpeg mpga audio/mpeg ms application/x-troff-ms msh model/mesh mv video/x-sgi-movie mxu video/vnd.mpegurl nc application/x-netcdf o application/octet-stream oda application/oda ogg application/ogg ogv video/ogg ogx application/ogg pac application/x-ns-proxy-autoconfig pbm image/x-portable-bitmap pdb chemical/x-pdb pdf application/pdf pgm image/x-portable-graymap pgn application/x-chess-pgn png image/png pnm image/x-portable-anymap ppm image/x-portable-pixmap ppt application/vnd.ms-powerpoint ps application/postscript qt video/quicktime ra audio/x-realaudio ram audio/x-pn-realaudio ras image/x-cmu-raster rdf application/rdf+xml rgb image/x-rgb rm audio/x-pn-realaudio roff application/x-troff rpm audio/x-pn-realaudio-plugin rss application/rss+xml rtf text/rtf; charset=%s rtx text/richtext; charset=%s sfx application/octet-stream sgm text/sgml; charset=%s sgml text/sgml; charset=%s sh application/x-sh shar application/x-shar sig application/pgp-signature silo model/mesh sit application/x-stuffit skd application/x-koan skm application/x-koan skp application/x-koan skt application/x-koan smi application/smil smil application/smil snd audio/basic so application/octet-stream spl application/x-futuresplash src application/x-wais-source stc application/vnd.sun.xml.calc.template std application/vnd.sun.xml.draw.template sti application/vnd.sun.xml.impress.template stw application/vnd.sun.xml.writer.template sv4cpio application/x-sv4cpio sv4crc application/x-sv4crc svg image/svg+xml svgx image/svg+xml svgz image/svg+xml swf application/x-shockwave-flash sxc application/vnd.sun.xml.calc sxd application/vnd.sun.xml.draw sxg application/vnd.sun.xml.writer.global sxi application/vnd.sun.xml.impress sxm application/vnd.sun.xml.math sxw application/vnd.sun.xml.writer t application/x-troff tar application/x-tar taz application/octet-stream tgz application/gzip tbz2 application/x-bzip2 txz application/x-xz tcl application/x-tcl tex application/x-tex texi application/x-texinfo texinfo application/x-texinfo tif image/tiff tiff image/tiff torrent application/x-bittorrent tr application/x-troff tsp application/dsptype tsv text/tab-separated-values; charset=%s txt text/plain; charset=%s tz application/octet-stream ustar application/x-ustar vcd application/x-cdlink vrml model/vrml vx video/x-rad-screenplay wav audio/x-wav wax audio/x-ms-wax wbmp image/vnd.wap.wbmp wbxml application/vnd.wap.wbxml webm video/webm webp image/webp wm video/x-ms-wm wma audio/x-ms-wma wmd application/x-ms-wmd wml text/vnd.wap.wml wmlc application/vnd.wap.wmlc wmls text/vnd.wap.wmlscript wmlsc application/vnd.wap.wmlscriptc wmv video/x-ms-wmv wmx video/x-ms-wmx wmz application/x-ms-wmz wrl model/vrml wsrc application/x-wais-source wvx video/x-ms-wvx xbm image/x-xbitmap xht application/xhtml+xml; charset=%s xhtml application/xhtml+xml; charset=%s xls application/vnd.ms-excel xml text/xml; charset=%s xpi application/x-xpinstall xpm image/x-xpixmap xsd text/xml; charset=%s xsl text/xml; charset=%s xslt text/xml; charset=%s xul application/vnd.mozilla.xul+xml xwd image/x-xwindowdump xz application/x-xz xyz chemical/x-xyz zip application/zip zoo application/octet-stream merecat-2.31+git20220513+ds/src/mmc.c000066400000000000000000000326351424066547300166120ustar00rootroot00000000000000/* mmc.c - mmap cache ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include "file.h" #include "libhttpd.h" #include "mmc.h" /* Defines. */ #ifndef DEFAULT_EXPIRE_AGE #define DEFAULT_EXPIRE_AGE 600 #endif #ifndef DESIRED_FREE_COUNT #define DESIRED_FREE_COUNT 100 #endif #ifndef DESIRED_MAX_MAPPED_FILES #define DESIRED_MAX_MAPPED_FILES 2000 #endif #ifndef DESIRED_MAX_MAPPED_BYTES #define DESIRED_MAX_MAPPED_BYTES 1000000000 #endif #ifndef INITIAL_HASH_SIZE #define INITIAL_HASH_SIZE (1 << 10) #endif #ifndef MAX #define MAX(a,b) ((a)>(b)?(a):(b)) #endif #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif struct map { struct map *next; ino_t ino; dev_t dev; time_t ctime; void *addr; off_t size; int refcount; time_t reftime; unsigned int hash; int hash_idx; }; /* Globals. */ static struct map *maps = NULL; static struct map *free_maps = NULL; static int alloc_count = 0, map_count = 0, free_count = 0; static struct map **hash_table = NULL; static int hash_size; static unsigned int hash_mask; static time_t expire_age = DEFAULT_EXPIRE_AGE; static off_t mapped_bytes = 0; /* Forwards. */ static void panic(void); static void really_unmap(struct map **mm); static int check_hash_size(void); static int add_hash(struct map *m); static struct map *find_hash(ino_t ino, dev_t dev, off_t size, time_t ctime); static unsigned int hash(ino_t ino, dev_t dev, off_t size, time_t ctime); #ifdef BUILTIN_ICONS #include "base64.h" struct { char *name; char *buf; } icons[] = { { "icons/back.gif", "R0lGODlhFAAWAMIAAP///8z//5mZmWZmZjMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRo" "ZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIg" "MTk5NQAh+QQBAAABACwAAAAAFAAWAAADSxi63P4jEPJqEDNTu6LO3PVpnDdOFnaCkHQGBTcqRRxu" "WG0v+5LrNUZQ8QPqeMakkaZsFihOpyDajMCoOoJAGNVWkt7QVfzokc+LBAA7" }, { "icons/blank.gif", "R0lGODlhFAAWAKEAAP///8z//wAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRoZSBwdWJsaWMgZG9t" "YWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIgMTk5NQAh+QQBAAAB" "ACwAAAAAFAAWAAACE4yPqcvtD6OctNqLs968+w+GSQEAOw==" }, { "icons/folder.gif", "R0lGODlhFAAWAMIAAP/////Mmcz//5lmMzMzMwAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRo" "ZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIg" "MTk5NQAh+QQBAAACACwAAAAAFAAWAAADVCi63P4wyklZufjOErrvRcR9ZKYpxUB6aokGQyzHKxyO" "9RoTV54PPJyPBewNSUXhcWc8soJOIjTaSVJhVphWxd3CeILUbDwmgMPmtHrNIyxM8Iw7AQA7" }, { "icons/generic.gif", "R0lGODlhFAAWAMIAAP///8z//5mZmTMzMwAAAAAAAAAAAAAAACH+TlRoaXMgYXJ0IGlzIGluIHRo" "ZSBwdWJsaWMgZG9tYWluLiBLZXZpbiBIdWdoZXMsIGtldmluaEBlaXQuY29tLCBTZXB0ZW1iZXIg" "MTk5NQAh+QQBAAABACwAAAAAFAAWAAADUDi6vPEwDECrnSO+aTvPEddVIriN1wWJKDG48IlSRG0T" "8kwJvIBLOkvvxwoCfDnjkaisIIHNZdL4LAarUSm0iY12uUwvcdArm3mvyG3N/iUAADs=" } }; static struct stat icost; static int cico = -1; static unsigned char icon[512]; /* Only small icons allowed atm */ int mmc_icon_check(char *filename, struct stat *st) { char *ptr; size_t i; cico = -1; ptr = strstr(filename, "icons/"); if (!ptr) return 0; for (i = 0; i < NELEMS(icons); i++) { off_t len; if (strcmp(ptr, icons[i].name)) continue; len = b64_decode(icons[i].buf, icon, sizeof(icon)); if (len <= 0) break; memset(&icost, 0, sizeof(icost)); icost.st_size = len; icost.st_ctim.tv_sec = 18446744073359756536UL; if (st) memcpy(st, &icost, sizeof(icost)); cico = i; return 1; } return 0; } /* Check if this is a small icon we have built-in */ static off_t mmc_icon_open(char *filename, char **buf, struct stat *st) { int found = 1; if (cico < 0 || cico >= (int)NELEMS(icons) || strcmp(icons[cico].name, filename)) found = mmc_icon_check(filename, st); if (!found) return -1; *buf = (char *)icon; *st = icost; return 0; } #else /* BUILTIN_ICONS */ int mmc_icon_check(char *filename, struct stat *st) { return 0; } static off_t mmc_icon_open(char *filename, char **buf, struct stat *st) { return -1; } #endif /* BUILTIN_ICONS */ void *mmc_map(char *filename, struct stat *st, struct timeval *tv) { struct stat sb; struct map *m; time_t now; char *buf = NULL; int fd; /* Stat the file, if necessary. */ if (!st) { st = &sb; if (stat(filename, &sb)) { syslog(LOG_ERR, "stat: %s", strerror(errno)); return NULL; } } /* Get the current time, if necessary. */ if (tv) now = tv->tv_sec; else now = time(NULL); /* See if we have it mapped already, via the hash table. */ if (check_hash_size() < 0) { syslog(LOG_ERR, "check_hash_size() failure"); return NULL; } m = find_hash(st->st_ino, st->st_dev, st->st_size, st->st_ctime); if (m) { /* Yep. Just return the existing map */ ++m->refcount; m->reftime = now; return m->addr; } /* Open the file. */ fd = open(filename, O_RDONLY); if (fd < 0) { if (mmc_icon_open(filename, &buf, &sb)) { syslog(LOG_ERR, "open: %s", strerror(errno)); return NULL; } } /* Find a free map entry or make a new one. */ if (free_maps) { m = free_maps; free_maps = m->next; --free_count; } else { m = malloc(sizeof(struct map)); if (!m) { if (!buf) close(fd); syslog(LOG_ERR, "out of memory allocating a struct map"); return NULL; } ++alloc_count; } /* Fill in the map entry. */ m->ino = st->st_ino; m->dev = st->st_dev; m->size = st->st_size; m->ctime = st->st_ctime; m->refcount = 1; m->reftime = now; /* Avoid doing anything for zero-length files; some systems don't like ** to mmap them, other systems dislike mallocing zero bytes. */ if (m->size == 0) { /* arbitrary non-NULL address */ m->addr = (void *)1; } else { size_t len = (size_t)m->size; /* loses on files >2GB */ if (buf) { m->addr = malloc(len); if (!m->addr) { syslog(LOG_ERR, "%s: %s", __func__, strerror(errno)); free(m); --alloc_count; return NULL; } memcpy(m->addr, buf, len); goto cont; } /* Map the file into memory. */ m->addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); if (m->addr == (void *)-1 && errno == ENOMEM) { /* Ooo, out of address space. Free all unreferenced maps ** and try again. */ panic(); m->addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); } if (m->addr == (void *)-1) { syslog(LOG_ERR, "mmap: %s", strerror(errno)); close(fd); free(m); --alloc_count; return NULL; } } close(fd); cont: /* Put the map into the hash table. */ if (add_hash(m) < 0) { syslog(LOG_ERR, "add_hash() failure"); free(m); --alloc_count; return NULL; } /* Put the map on the active list. */ m->next = maps; maps = m; ++map_count; /* Update the total byte count. */ mapped_bytes += m->size; /* And return the address. */ return m->addr; } void mmc_unmap(void *addr, struct stat *st, struct timeval *tv) { struct map *m = NULL; /* Find the map entry for this address. First try a hash. */ if (st) { m = find_hash(st->st_ino, st->st_dev, st->st_size, st->st_ctime); if (m && m->addr != addr) m = NULL; } /* If that didn't work, try a full search. */ if (!m) { for (m = maps; m; m = m->next) { if (m->addr == addr) break; } } if (!m) { syslog(LOG_ERR, "mmc_unmap failed to find entry!"); return; } if (m->refcount <= 0) { syslog(LOG_ERR, "mmc_unmap found zero or negative refcount!"); return; } --m->refcount; if (tv) m->reftime = tv->tv_sec; else m->reftime = time(NULL); } void mmc_cleanup(struct timeval *tv) { struct map **mm; struct map *m; time_t now; /* Get the current time, if necessary. */ if (tv) now = tv->tv_sec; else now = time(NULL); /* Really unmap any unreferenced entries older than the age limit. */ for (mm = &maps; *mm;) { m = *mm; if (m->refcount == 0 && now - m->reftime >= expire_age) really_unmap(mm); else mm = &(*mm)->next; } /* Adjust the age limit if there are too many bytes mapped, or ** too many or too few files mapped. */ if (mapped_bytes > DESIRED_MAX_MAPPED_BYTES) expire_age = MAX((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10); else if (map_count > DESIRED_MAX_MAPPED_FILES) expire_age = MAX((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10); else if (map_count < DESIRED_MAX_MAPPED_FILES / 2) expire_age = MIN((expire_age * 5) / 4, DEFAULT_EXPIRE_AGE * 3); /* Really free excess blocks on the free list. */ while (free_count > DESIRED_FREE_COUNT) { m = free_maps; free_maps = m->next; free(m); --free_count; --alloc_count; } } static void panic(void) { struct map **mm; struct map *m; syslog(LOG_ERR, "mmc panic - freeing all unreferenced maps"); /* Really unmap all unreferenced entries. */ for (mm = &maps; *mm;) { m = *mm; if (m->refcount == 0) really_unmap(mm); else mm = &(*mm)->next; } } static void really_unmap(struct map **mm) { struct map *m; m = *mm; if (m->size > 0) { if (!m->ino) /* Only real files have an inode, free built-in icon data */ free(m->addr); else if (-1 == munmap(m->addr, m->size)) syslog(LOG_ERR, "munmap(): %s", strerror(errno)); } /* Update the total byte count. */ mapped_bytes -= m->size; /* And move the map to the free list. */ *mm = m->next; --map_count; m->next = free_maps; free_maps = m; ++free_count; /* This will sometimes break hash chains, but that's harmless; the ** unmapping code that searches the hash table knows to keep searching. */ hash_table[m->hash_idx] = NULL; } void mmc_destroy(void) { struct map *m; while (maps) really_unmap(&maps); while (free_maps) { m = free_maps; free_maps = m->next; --free_count; --alloc_count; free(m); } if (hash_table) free(hash_table); } /* Make sure the hash table is big enough. */ static int check_hash_size(void) { struct map *m; int i; /* At least three times bigger than the number of entries? */ if (hash_table && hash_size >= map_count * 3) return 0; /* Are we just starting out? */ if (!hash_table) { hash_size = INITIAL_HASH_SIZE; hash_mask = hash_size - 1; } else { /* No, got to expand. */ free(hash_table); /* Double the hash size until it's big enough. */ while (hash_size < map_count * 6) hash_size = hash_size << 1; hash_mask = hash_size - 1; } /* Make the new table. */ hash_table = malloc(hash_size * sizeof(struct map *)); if (!hash_table) return -1; /* Clear it. */ for (i = 0; i < hash_size; ++i) hash_table[i] = NULL; /* And rehash all entries. */ for (m = maps; m; m = m->next) { if (add_hash(m) < 0) return -1; } return 0; } static int add_hash(struct map *m) { unsigned int h, he, i; h = hash(m->ino, m->dev, m->size, m->ctime); he = (h + hash_size - 1) & hash_mask; for (i = h;; i = (i + 1) & hash_mask) { if (!hash_table[i]) { hash_table[i] = m; m->hash = h; m->hash_idx = i; return 0; } if (i == he) break; } return -1; } static struct map *find_hash(ino_t ino, dev_t dev, off_t size, time_t ctime) { unsigned int h, he, i; struct map *m; h = hash(ino, dev, size, ctime); he = (h + hash_size - 1) & hash_mask; for (i = h;; i = (i + 1) & hash_mask) { m = hash_table[i]; if (!m) break; if (m->hash == h && m->ino == ino && m->dev == dev && m->size == size && m->ctime == ctime) return m; if (i == he) break; } return NULL; } static unsigned int hash(ino_t ino, dev_t dev, off_t size, time_t ctime) { unsigned int h = 177573; h ^= ino; h += h << 5; h ^= dev; h += h << 5; h ^= size; h += h << 5; h ^= ctime; return h & hash_mask; } /* Generate debugging statistics syslog message. */ void mmc_logstats(long secs) { syslog(LOG_INFO, "map cache - %d allocated, %d active (%lld bytes), " "%d free; hash size: %d; expire age: %lld", alloc_count, map_count, (long long)mapped_bytes, free_count, hash_size, (long long)expire_age); if (map_count + free_count != alloc_count) syslog(LOG_ERR, "map counts don't add up!"); } merecat-2.31+git20220513+ds/src/mmc.h000066400000000000000000000047251424066547300166160ustar00rootroot00000000000000/* mmc.h - header file for mmap cache package ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MMC_H_ #define MMC_H_ /* Checks if filename is a built-in icon */ extern int mmc_icon_check(char *filename, struct stat *st); /* Returns an mmap()ed area for the given file, or (void*) 0 on errors. ** If you have a stat buffer on the file, pass it in, otherwise pass 0. ** Same for the current time. */ extern void *mmc_map(char *filename, struct stat *sbP, struct timeval *nowP); /* Done with an mmap()ed area that was returned by mmc_map(). ** If you have a stat buffer on the file, pass it in, otherwise pass 0. ** Same for the current time. */ extern void mmc_unmap(void *addr, struct stat *sbP, struct timeval *nowP); /* Clean up the mmc package, freeing any unused storage. ** This should be called periodically, say every five minutes. ** If you have the current time, pass it in, otherwise pass 0. */ extern void mmc_cleanup(struct timeval *nowP); /* Free all storage, usually in preparation for exitting. */ extern void mmc_destroy(void); /* Generate debugging statistics syslog message. */ extern void mmc_logstats(long secs); #endif /* MMC_H_ */ merecat-2.31+git20220513+ds/src/pidfile.c000066400000000000000000000071541424066547300174500ustar00rootroot00000000000000/* Updated by troglobit for libite/finit/uftpd projects 2016/07/04 */ /* $OpenBSD: pidfile.c,v 1.11 2015/06/03 02:24:36 millert Exp $ */ /* $NetBSD: pidfile.c,v 1.4 2001/02/19 22:43:42 cgd Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* utimensat() */ #include /* utimensat() on *BSD */ #include #include #include #include #include #include "merecat.h" static char *pidfile_path = NULL; static pid_t pidfile_pid = 0; static void pidfile_cleanup(void); const char *__pidfile_path = LOCALSTATEDIR "/run"; const char *__pidfile_name = NULL; int pidfile(const char *basename) { int save_errno; int atexit_already; pid_t pid; FILE *f; if (basename == NULL) basename = prognm; pid = getpid(); atexit_already = 0; if (pidfile_path != NULL) { if (!access(pidfile_path, R_OK) && pid == pidfile_pid) { utimensat(0, pidfile_path, NULL, 0); return (0); } free(pidfile_path); pidfile_path = NULL; __pidfile_name = NULL; atexit_already = 1; } if (basename[0] != '/') { if (asprintf(&pidfile_path, "%s/%s.pid", __pidfile_path, basename) == -1) return (-1); } else { if (asprintf(&pidfile_path, "%s", basename) == -1) return (-1); } if ((f = fopen(pidfile_path, "w")) == NULL) { save_errno = errno; free(pidfile_path); pidfile_path = NULL; errno = save_errno; return (-1); } if (fprintf(f, "%ld\n", (long)pid) <= 0 || fflush(f) != 0) { save_errno = errno; fclose(f); unlink(pidfile_path); free(pidfile_path); pidfile_path = NULL; errno = save_errno; return (-1); } fclose(f); __pidfile_name = pidfile_path; /* * LITE extension, no need to set up another atexit() handler * if user only called us to update the mtime of the PID file */ if (atexit_already) return (0); pidfile_pid = pid; if (atexit(pidfile_cleanup) < 0) { save_errno = errno; unlink(pidfile_path); free(pidfile_path); pidfile_path = NULL; pidfile_pid = 0; errno = save_errno; return (-1); } return (0); } static void pidfile_cleanup(void) { if (pidfile_path != NULL && pidfile_pid == getpid()) { unlink(pidfile_path); free(pidfile_path); pidfile_path = NULL; } } merecat-2.31+git20220513+ds/src/srv.c000066400000000000000000000155721424066547300166510ustar00rootroot00000000000000/* Start, stop, and act on a single HTTP server ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "fdwatch.h" #include "libhttpd.h" #include "merecat.h" #include "srv.h" #include "ssl.h" extern int handle_newconnect(struct httpd *hs, struct timeval *tv, int fd); static void lookup_hostname(char *hostname, uint16_t port, sockaddr_t *sa4, size_t sa4_len, int *gotv4, sockaddr_t *sa6, size_t sa6_len, int *gotv6) { #ifdef USE_IPV6 struct addrinfo hints; char service[10]; int gaierr; struct addrinfo *ai; struct addrinfo *ptr; struct addrinfo *aiv6; struct addrinfo *aiv4; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; snprintf(service, sizeof(service), "%d", port); if ((gaierr = getaddrinfo(hostname, service, &hints, &ai)) != 0) { syslog(LOG_CRIT, "getaddrinfo %s: %s", hostname, gai_strerror(gaierr)); exit(1); } /* Find the first IPv6 and IPv4 entries. */ aiv6 = NULL; aiv4 = NULL; for (ptr = ai; ptr; ptr = ptr->ai_next) { switch (ptr->ai_family) { case AF_INET6: if (!aiv6) aiv6 = ptr; break; case AF_INET: if (!aiv4) aiv4 = ptr; break; } } if (!aiv6) { *gotv6 = 0; } else { if (sa6_len < aiv6->ai_addrlen) { syslog(LOG_CRIT, "%s - sockaddr too small (%lu < %lu)", hostname, (unsigned long)sa6_len, (unsigned long)aiv6->ai_addrlen); exit(1); } memset(sa6, 0, sa6_len); memmove(sa6, aiv6->ai_addr, aiv6->ai_addrlen); *gotv6 = 1; } #ifdef __linux__ /* * On Linux listening to IN6ADDR_ANY_INIT means also listening * to INADDR_ANY, so for this special case we do not need to * try to bind() to both. In fact, it will cause an error. */ if (!aiv4 || (aiv6 && !hostname)) #else if (!aiv4) #endif *gotv4 = 0; else { if (sa4_len < aiv4->ai_addrlen) { syslog(LOG_CRIT, "%s - sockaddr too small (%lu < %lu)", hostname, (unsigned long)sa4_len, (unsigned long)aiv4->ai_addrlen); exit(1); } memset(sa4, 0, sa4_len); memmove(sa4, aiv4->ai_addr, aiv4->ai_addrlen); *gotv4 = 1; } freeaddrinfo(ai); #else /* USE_IPV6 */ struct hostent *he; *gotv6 = 0; memset(sa4, 0, sa4_len); sa4->sa.sa_family = AF_INET; if (!hostname) { sa4->sa_in.sin_addr.s_addr = htonl(INADDR_ANY); } else { sa4->sa_in.sin_addr.s_addr = inet_addr(hostname); if ((int)sa4->sa_in.sin_addr.s_addr == -1) { he = gethostbyname(hostname); if (!he) { #ifdef HAVE_HSTRERROR syslog(LOG_CRIT, "gethostbyname %s: %s", hostname, hstrerror(h_errno)); #else syslog(LOG_CRIT, "gethostbyname %s failed", hostname); #endif exit(1); } if (he->h_addrtype != AF_INET) { syslog(LOG_CRIT, "%s - non-IP network address", hostname); exit(1); } memmove(&sa4->sa_in.sin_addr.s_addr, he->h_addr, he->h_length); } } sa4->sa_in.sin_port = htons(port); *gotv4 = 1; #endif /* USE_IPV6 */ } struct httpd *srv_init(struct srv *srv) { struct httpd *hs; sockaddr_t sa4; sockaddr_t sa6; size_t i; void *ctx = NULL; int gotv4, gotv6; syslog(LOG_DEBUG, "Initializing server %s: port %d, ssl %s, path %s", srv->title, srv->port, srv->ssl ? "on" : "off", srv->path); /* Resolve default port */ if (!srv->port) srv->port = srv->ssl ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT; /* Look up hostname now, in case we chroot(). */ lookup_hostname(srv->host, srv->port, &sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6); if (!(gotv4 || gotv6)) { syslog(LOG_ERR, "cannot find any valid address"); exit(1); } /* Initialize SSL library and load cert files before we chroot */ if (srv->ssl) { ctx = httpd_ssl_init(srv->certfile, srv->keyfile, srv->dhfile, srv->ssl_proto, srv->ciphers); if (!ctx) { httpd_ssl_log_errors(); exit(1); } } /* Initialize the HTTP layer. Got to do this before giving up root, ** so that we can bind to a privileged port. */ hs = httpd_init(hostname, srv->port, ctx, charset, max_age, srv->path, 0, no_symlink_check, do_vhost, do_global_passwd, url_pattern, local_pattern, no_empty_referers, do_list_dotfiles); if (!hs) goto err; if (httpd_cgi_init(hs, cgi_enabled, cgi_pattern, cgi_limit)) goto release; for (i = 0; i < NELEMS(srv->redirect); i++) httpd_redirect_add(hs, srv->redirect[i].code, srv->redirect[i].pattern, srv->redirect[i].location); for (i = 0; i < NELEMS(srv->location); i++) httpd_location_add(hs, srv->location[i].pattern, srv->location[i].path); if (httpd_listen(hs, gotv4 ? &sa4 : NULL, gotv6 ? &sa6 : NULL)) goto err; return hs; release: srv_exit(hs); err: syslog(LOG_CRIT, "Failed initializing server %s", srv->title); return NULL; } void srv_start(struct httpd *hs) { if (hs->listen4_fd != -1) fdwatch_add_fd(hs->listen4_fd, NULL, FDW_READ); if (hs->listen6_fd != -1) fdwatch_add_fd(hs->listen6_fd, NULL, FDW_READ); } void srv_stop(struct httpd *hs) { if (hs->listen4_fd != -1) fdwatch_del_fd(hs->listen4_fd); if (hs->listen6_fd != -1) fdwatch_del_fd(hs->listen6_fd); httpd_unlisten(hs); } int srv_connect(struct httpd *hs, struct timeval *tv) { if (!hs) return 0; /* Is it a new connection? */ if (hs->listen6_fd != -1 && fdwatch_check_fd(hs->listen6_fd)) { if (handle_newconnect(hs, tv, hs->listen6_fd)) return 1; } if (hs->listen4_fd != -1 && fdwatch_check_fd(hs->listen4_fd)) { if (handle_newconnect(hs, tv, hs->listen4_fd)) return 1; } return 0; } void srv_exit(struct httpd *hs) { srv_stop(hs); httpd_exit(hs); } merecat-2.31+git20220513+ds/src/srv.h000066400000000000000000000050261424066547300166470ustar00rootroot00000000000000/* Start, stop, and act on a single HTTP server ** ** Copyright (C) 1995-2015 Jef Poskanzer ** Copyright (C) 2016-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef SRV_H_ #define SRV_H_ #define MAX_REDIRECTS 2 #define MAX_LOCATIONS 2 struct srv { char *title; char *host; /* specific virtual-host, unused for now */ uint16_t port; /* Server listening port */ char *path; /* path within chroot/server dir, unused for now */ int ssl; /* HTTPS or HTTP */ char *ssl_proto; char *ciphers; char *certfile; char *keyfile; char *dhfile; struct { char *pattern; /* Pattern to match() against */ int code; /* HTTP status code, default: 301 */ char *location; /* Location: to redirect to, supports format specifiers */ } redirect[MAX_REDIRECTS]; struct { char *pattern; /* Pattern to match() against */ char *path; /* Path to use for matching requests */ } location[MAX_LOCATIONS]; }; struct httpd *srv_init (struct srv *srv); void srv_exit (struct httpd *hs); void srv_start (struct httpd *hs); void srv_stop (struct httpd *hs); int srv_connect(struct httpd *hs, struct timeval *tv); #endif /* SRV_H_ */ merecat-2.31+git20220513+ds/src/ssl.c000066400000000000000000000237141424066547300166350ustar00rootroot00000000000000/* ssl.c - HTTPS support functions ** ** Copyright (C) 2017-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libhttpd.h" #include "file.h" #include "ssl.h" static int proto_to_version(char *proto) { struct { char *proto; int version; } supported[] = { { "SSLv3", SSL3_VERSION }, /* 0x300 */ { "TLSv1", TLS1_VERSION }, /* 0x301 */ { "TLSv1.1", TLS1_1_VERSION }, /* 0x302 */ { "TLSv1.2", TLS1_2_VERSION }, /* 0x303 */ { "TLSv1.3", TLS1_3_VERSION }, /* 0x304 */ // { "TLSv1.4", TLS1_4_VERSION }, /* 0x305 */ { NULL, 0 } }; int i, version; /* User is Kimi RäikkÃļnen */ if (!strncmp(proto, "0x3", 3)) { errno = 0; version = strtoul(proto, NULL, 0); if (errno) return -1; return version; } for (i = 0; supported[i].proto; i++) { if (!strcmp(proto, supported[i].proto)) return supported[i].version; } return -1; } static void append(char *str, const char *c, size_t len) { if (strlen(str) + strlen(c) + 1 >= len) return; if (str[0]) strcat(str, ":"); strcat(str, c); } static void split_ciphers(char *orig, char **list, char **suite) { size_t len; char *str, *pre, *post, *c; len = strlen(orig) + 1; str = strdup(orig); pre = calloc(1, len); post = calloc(1, len); if (str && pre && post) { c = strtok(str, ":"); while (c) { if (strchr(c, '_')) append(post, c, len); else append(pre, c, len); c = strtok(NULL, ":"); } } if (str) free(str); *list = pre; *suite = post; } static void dump_supported_ciphers(SSL_CTX *ctx) { STACK_OF(SSL_CIPHER) *ciphers; const SSL_CIPHER *cipher; size_t len; char *buf; int i, num; ciphers = SSL_CTX_get_ciphers(ctx); if (!ciphers) { syslog(LOG_WARNING, "No SSL ciphers set up!"); return; } num = sk_SSL_CIPHER_num(ciphers); if (num <= 0) { buf = strdup("none"); goto error; } buf = calloc(num, 50); if (!buf) return; len = num * 50; for (i = 0; i < num; i++) { cipher = sk_SSL_CIPHER_value(ciphers, i); append(buf, SSL_CIPHER_get_name(cipher), len); } error: syslog(LOG_DEBUG, "SSL ciphers enabled: %s", buf); free(buf); } void *httpd_ssl_init(char *cert, char *key, char *dhparm, char *proto, char *ciphers) { SSL_CTX *ctx; char *list, *suite; int min_version, rc = 0; ctx = SSL_CTX_new(SSLv23_method()); if (!ctx) return NULL; /* Disable insecure SSL/TLS versions: * * - SSLv2 has the DROWN vulnerability * - SSLv3 was POODLE * - TLSv1 had BEAST * * ... and then we had CRIME, which forced us to disable * compression. All these required SSL_CTX_set_options(), but * OpenSSL v1.1.0 recommends SSL_CTX_set_min_proto_version(), * which is far easier to understand as an end-user. Also, * compression is disabled by default in OpenSSL v1.1.0, which * is what the configure script now requires. */ min_version = proto_to_version(proto); if (-1 == min_version) { syslog(LOG_ERR, "Unknown SSL protocol '%s'", proto); goto error; } SSL_CTX_set_min_proto_version(ctx, min_version); if (ciphers) { split_ciphers(ciphers, &list, &suite); if (list) { rc += SSL_CTX_set_cipher_list(ctx, list); free(list); } if (suite) { rc += SSL_CTX_set_ciphersuites(ctx, suite); free(suite); } if (!rc) { syslog(LOG_ERR, "Invalid SSL ciphers '%s'", ciphers); goto error; } } dump_supported_ciphers(ctx); /* Best practices: prefer our ciphers over the client's proposed */ SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); // /* Enable OCSP stapling, include OCSP validation message in TLS hand-shake */ // SSL_CTX_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp); // SSL_CTX_set_tlsext_status_cb(ctx, ocsp_status_cb); SSL_CTX_set_default_verify_paths(ctx); SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); if (SSL_CTX_use_certificate_chain_file(ctx, cert) != 1) { syslog(LOG_ERR, "Invalid SSL cert '%s'", cert); goto error; } if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) != 1) { syslog(LOG_ERR, "Invalid SSL key '%s'", key); goto error; } if (dhparm) { FILE *fp; DH *dh = NULL; fp = fopen(dhparm, "r"); if (!fp) { syslog(LOG_ERR, "Failed opening dhfile %s: %s", dhparm, strerror(errno)); return ctx; } dh = PEM_read_DHparams(fp, NULL, NULL, NULL); fclose(fp); if (!dh || SSL_CTX_set_tmp_dh(ctx, dh) != 1) httpd_ssl_log_errors(); } return ctx; error: SSL_CTX_free(ctx); return NULL; } void httpd_ssl_exit(struct httpd *hs) { if (!hs || !hs->ctx) return; SSL_CTX_free(hs->ctx); hs->ctx = NULL; ENGINE_cleanup(); CRYPTO_cleanup_all_ex_data(); CONF_modules_free(); CONF_modules_unload(1); // This function is deprecated since OpenSSL 1.1.0, removed in recent versions. #if OPENSSL_VERSION_NUMBER < 0x10100000L COMP_zlib_cleanup(); #endif } /* * Translates OpenSSL error code/status to human readable text. * Skips prototol (hacking) errors, connection reset, and the * odd cases where SSL_ERROR_SYSCALL and errno is unset. */ static int status(struct http_conn *hc, int rc) { static char errmsg[80]; hc->errmsg = NULL; if (rc > 0) return 0; rc = SSL_get_error(hc->ssl, rc); switch (rc) { case SSL_ERROR_SSL: /* rc = 1 */ // log ERR_get_error()? errno = EPROTO; goto leave; case SSL_ERROR_WANT_READ: /* rc = 2 */ case SSL_ERROR_WANT_WRITE: /* rc = 3 */ case SSL_ERROR_WANT_X509_LOOKUP: /* rc = 4 */ case SSL_ERROR_WANT_CONNECT: /* rc = 7 */ case SSL_ERROR_WANT_ACCEPT: /* rc = 8 */ errno = EAGAIN; break; case SSL_ERROR_SYSCALL: /* rc = 5 */ /* errno set already. */ if (errno != 0 && errno != ECONNRESET && errno != EPROTO) hc->errmsg = strerror(errno); goto leave; default: errno = EINVAL; break; } if (!hc->errmsg) { snprintf(errmsg, sizeof(errmsg), "%s, code %d", ERR_reason_error_string(rc) ?: "unknown error", rc); hc->errmsg = errmsg; } leave: return -1; } /* ** Poll underlying fd and call SSL_accept() as long as it ** wants more ... or until our patience runs out. */ static int accept_connection(struct http_conn *hc) { struct pollfd pfd = { .events = POLLIN, .fd = hc->conn_fd, }; int rc, retries = 5; retry: rc = poll(&pfd, 1, 100); if (rc > 0) { rc = status(hc, SSL_accept(hc->ssl)); if (-1 == rc && EAGAIN == errno) goto retry; return rc; } if (rc < 0) { hc->errmsg = strerror(errno); return -1; } if (--retries > 0) goto retry; hc->errmsg = "client timeout"; return -1; } int httpd_ssl_open(struct http_conn *hc) { SSL_CTX *ctx = NULL; if (!hc) { errno = EINVAL; return -1; } hc->ssl = NULL; if (hc->hs) ctx = hc->hs->ctx; if (ctx) { hc->ssl = SSL_new(ctx); if (!hc->ssl) { hc->errmsg = "creating connection"; return 1; } if (-1 == httpd_set_ndelay(hc->conn_fd)) syslog(LOG_ERR, "Failed setting SSL non-blocking: %s", strerror(errno)); SSL_set_fd(hc->ssl, hc->conn_fd); if (-1 == accept_connection(hc)) { ERR_clear_error(); SSL_free(hc->ssl); return 1; } } return 0; } void httpd_ssl_close(struct http_conn *hc) { if (hc->ssl) SSL_free(hc->ssl); hc->ssl = NULL; } void httpd_ssl_shutdown(struct http_conn *hc) { if (hc->ssl) SSL_shutdown(hc->ssl); } static int ssl_error_cb(const char *str, size_t len, void *data) { size_t sz; char buf[512]; memset(buf, 0, sizeof(buf)); sz = len < sizeof(buf) ? len : sizeof(buf) - 1; memcpy(buf, str, sz); syslog(LOG_DEBUG, "OpenSSL error: %s", buf); return 0; } void httpd_ssl_log_errors(void) { ERR_print_errors_cb(ssl_error_cb, NULL); } ssize_t httpd_ssl_read(struct http_conn *hc, void *buf, size_t len) { int rc = SSL_read(hc->ssl, buf, len); if (status(hc, rc)) return -1; return rc; } ssize_t httpd_ssl_write(struct http_conn *hc, void *buf, size_t len) { int rc = SSL_write(hc->ssl, buf, len); if (status(hc, rc)) return -1; return rc; } ssize_t httpd_ssl_writev(struct http_conn *hc, struct iovec *iov, int num) { ssize_t sum = 0; int i; /* * on retry, SSL_write arguments must be EXACTLY the same, or * will get 0x1409f07f "bad write retry" * so try separate SSL_writes for each writev element * to avoid malloc'ing and holding buffer */ for (i = 0; i < num; i++) { int rc = SSL_write(hc->ssl, iov[i].iov_base, iov[i].iov_len); if (status(hc, rc)) { if (sum == 0) return -1; else return sum; } // rc *SHOULD* be == iov[i].iov_len sum += rc; } return sum; } merecat-2.31+git20220513+ds/src/ssl.h000066400000000000000000000054241424066547300166400ustar00rootroot00000000000000/* ssl.c - HTTPS support functions ** ** Copyright (C) 2017-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MERECAT_SSL_H_ #define MERECAT_SSL_H_ #include #include #include "libhttpd.h" #ifdef ENABLE_SSL /* Initialize SSL and load certificate and key file */ void *httpd_ssl_init(char *cert, char *key, char *dhparm, char *proto, char *ciphers); /* Unload SSL, called automatically at httpd_exit() */ void httpd_ssl_exit(struct httpd *hs); /* Open a new HTTPS connection */ int httpd_ssl_open(struct http_conn *hc); /* Close a HTTP/HTTPS connection */ void httpd_ssl_close(struct http_conn *hc); /* Called before httpd_ssl_close() to signal connection shut down */ void httpd_ssl_shutdown(struct http_conn *hc); /* Reads SSL error log and sends to syslog */ void httpd_ssl_log_errors(void); /* Wrappers for read()/write() and writev() */ ssize_t httpd_ssl_read (struct http_conn *hc, void *buf, size_t len); ssize_t httpd_ssl_write (struct http_conn *hc, void *buf, size_t len); ssize_t httpd_ssl_writev (struct http_conn *hc, struct iovec *iov, int num); #else #define httpd_ssl_init(cert, key, dhparm, proto, ciphers) NULL #define httpd_ssl_exit(hs) #define httpd_ssl_open(hc) (hc->ssl = NULL) #define httpd_ssl_close(hc) #define httpd_ssl_shutdown(hc) #define httpd_ssl_log_errors() #define httpd_ssl_read(hc, buf, len) -1 #define httpd_ssl_write(hc, buf, len) -1 #define httpd_ssl_writev(hc, iov, num) -1 #endif #endif /* MERECAT_SSL_H_ */ merecat-2.31+git20220513+ds/src/stack.c000066400000000000000000000052161424066547300171360ustar00rootroot00000000000000/* Simple, stupid and silly stack probe :P ** ** Copyright (C) 2017-2021 Joachim Wiberg ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifdef HAVE_BACKTRACE #include #include #include #include #include /* readlink() */ #include /* backtrace() */ /* From The Practice of Programming, by Kernighan and Pike */ #ifndef NELEMS #define NELEMS(array) (sizeof(array) / sizeof(array[0])) #endif static char *addr2line(char *exec, char *addr) { static char buf[512]; FILE *fp; snprintf(buf, sizeof(buf), "addr2line -e %s %s", exec, addr); fp = popen(buf, "r"); if (!fp) return NULL; fgets(buf, sizeof(buf), fp); pclose(fp); return buf; } /* * Build with: ./configure CFLAGS="-g -Og -rdynamic" */ void stack_trace(void) { char **messages; void *trace[16]; char exec[256] = { 0 }; int i, rc, len; rc = readlink("/proc/self/exe", exec, sizeof(exec)); if (-1 == rc) return; len = backtrace(trace, NELEMS(trace)); messages = backtrace_symbols(trace, len); if (!messages) return; syslog(LOG_NOTICE, ">>> STACK TRACE"); for (i = 0; i < len; i++) { char *line; line = strstr(messages[i], " [0x"); if (line) line = addr2line(exec, line + 2); if (!line) line = ""; syslog(LOG_NOTICE, ">>> %s%s", messages[i], line); } free(messages); } #endif /* HAVE_BACKTRACE */ merecat-2.31+git20220513+ds/src/tdate_parse.c000066400000000000000000000172161424066547300203270ustar00rootroot00000000000000/* tdate_parse - parse string dates into internal form, stripped-down version ** ** Copyright (C) 1995 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ /* This is a stripped-down version of date_parse.c, available at ** http://www.acme.com/software/date_parse/ */ #include #include #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include #include #include "tdate_parse.h" struct strlong { char *s; long l; }; static void pound_case(char *str) { for (; *str != '\0'; ++str) { if (isupper((int)*str)) *str = tolower((int)*str); } } static int strlong_compare(v1, v2) char *v1; char *v2; { return strcmp(((struct strlong *)v1)->s, ((struct strlong *)v2)->s); } static int strlong_search(char *str, struct strlong *tab, int n, long *lP) { int i, h, l, r; l = 0; h = n - 1; for (;;) { i = (h + l) / 2; r = strcmp(str, tab[i].s); if (r < 0) { h = i - 1; } else if (r > 0) { l = i + 1; } else { *lP = tab[i].l; return 1; } if (h < l) return 0; } } static int scan_wday(char *str_wday, long *tm_wdayP) { static struct strlong wday_tab[] = { {"sun", 0}, {"sunday", 0}, {"mon", 1}, {"monday", 1}, {"tue", 2}, {"tuesday", 2}, {"wed", 3}, {"wednesday", 3}, {"thu", 4}, {"thursday", 4}, {"fri", 5}, {"friday", 5}, {"sat", 6}, {"saturday", 6}, }; static int sorted = 0; if (!sorted) { qsort(wday_tab, sizeof(wday_tab) / sizeof(struct strlong), sizeof(struct strlong), strlong_compare); sorted = 1; } pound_case(str_wday); return strlong_search(str_wday, wday_tab, sizeof(wday_tab) / sizeof(struct strlong), tm_wdayP); } static int scan_mon(char *str_mon, long *tm_monP) { static struct strlong mon_tab[] = { {"jan", 0}, {"january", 0}, {"feb", 1}, {"february", 1}, {"mar", 2}, {"march", 2}, {"apr", 3}, {"april", 3}, {"may", 4}, {"jun", 5}, {"june", 5}, {"jul", 6}, {"july", 6}, {"aug", 7}, {"august", 7}, {"sep", 8}, {"september", 8}, {"oct", 9}, {"october", 9}, {"nov", 10}, {"november", 10}, {"dec", 11}, {"december", 11}, }; static int sorted = 0; if (!sorted) { qsort(mon_tab, sizeof(mon_tab) / sizeof(struct strlong), sizeof(struct strlong), strlong_compare); sorted = 1; } pound_case(str_mon); return strlong_search(str_mon, mon_tab, sizeof(mon_tab) / sizeof(struct strlong), tm_monP); } static int is_leap(int year) { return (year % 400) ? ((year % 100) ? ((year % 4) ? 0 : 1) : 0) : 1; } /* Basically the same as mktime(). */ static time_t tm_to_time(struct tm *tmP) { time_t t; static int monthtab[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; /* Years since epoch, converted to days. */ t = (tmP->tm_year - 70) * 365; /* Leap days for previous years. */ t += (tmP->tm_year - 69) / 4; /* Days for the beginning of this month. */ t += monthtab[tmP->tm_mon]; /* Leap day for this year. */ if (tmP->tm_mon >= 2 && is_leap(tmP->tm_year + 1900)) ++t; /* Days since the beginning of this month. */ t += tmP->tm_mday - 1; /* 1-based field */ /* Hours, minutes, and seconds. */ t = t * 24 + tmP->tm_hour; t = t * 60 + tmP->tm_min; t = t * 60 + tmP->tm_sec; return t; } time_t tdate_parse(char *str) { struct tm tm; char *cp; char str_mon[500], str_wday[500]; int tm_sec, tm_min, tm_hour, tm_mday, tm_year; long tm_mon, tm_wday; time_t t; /* Initialize. */ memset(&tm, 0, sizeof(struct tm)); /* Skip initial whitespace ourselves - sscanf is clumsy at this. */ for (cp = str; *cp == ' ' || *cp == '\t'; ++cp) continue; /* And do the sscanfs. WARNING: you can add more formats here, ** but be careful! You can easily screw up the parsing of existing ** formats when you add new ones. The order is important. */ /* DD-mth-YY HH:MM:SS GMT */ if (sscanf(cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT", &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 6 && scan_mon(str_mon, &tm_mon)) { tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* DD mth YY HH:MM:SS GMT */ else if (sscanf(cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT", &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 6 && scan_mon(str_mon, &tm_mon)) { tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* HH:MM:SS GMT DD-mth-YY */ else if (sscanf(cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d", &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, &tm_year) == 6 && scan_mon(str_mon, &tm_mon)) { tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; } /* HH:MM:SS GMT DD mth YY */ else if (sscanf(cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d", &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, &tm_year) == 6 && scan_mon(str_mon, &tm_mon)) { tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; } /* wdy, DD-mth-YY HH:MM:SS GMT */ else if (sscanf(cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d GMT", str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 7 && scan_wday(str_wday, &tm_wday) && scan_mon(str_mon, &tm_mon)) { tm.tm_wday = tm_wday; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* wdy, DD mth YY HH:MM:SS GMT */ else if (sscanf(cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d GMT", str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, &tm_sec) == 7 && scan_wday(str_wday, &tm_wday) && scan_mon(str_mon, &tm_mon)) { tm.tm_wday = tm_wday; tm.tm_mday = tm_mday; tm.tm_mon = tm_mon; tm.tm_year = tm_year; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; } /* wdy mth DD HH:MM:SS GMT YY */ else if (sscanf(cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d", str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec, &tm_year) == 7 && scan_wday(str_wday, &tm_wday) && scan_mon(str_mon, &tm_mon)) { tm.tm_wday = tm_wday; tm.tm_mon = tm_mon; tm.tm_mday = tm_mday; tm.tm_hour = tm_hour; tm.tm_min = tm_min; tm.tm_sec = tm_sec; tm.tm_year = tm_year; } else return (time_t)-1; if (tm.tm_year > 1900) tm.tm_year -= 1900; else if (tm.tm_year < 70) tm.tm_year += 100; t = tm_to_time(&tm); return t; } merecat-2.31+git20220513+ds/src/tdate_parse.h000066400000000000000000000030671424066547300203330ustar00rootroot00000000000000/* tdate_parse.h - parse string dates into internal form, stripped-down version ** ** Copyright (C) 1995 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _TDATE_PARSE_H_ #define _TDATE_PARSE_H_ extern time_t tdate_parse(char *str); #endif /* _TDATE_PARSE_H_ */ merecat-2.31+git20220513+ds/src/timers.c000066400000000000000000000210051424066547300173260ustar00rootroot00000000000000/* timers.c - simple timer routines ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "timers.h" #define HASH_SIZE 67 static struct timer *timers[HASH_SIZE]; static struct timer *free_timers; static int alloc_count, active_count, free_count; arg_t noarg; #undef HAVE_CLOCK_MONO #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) #define HAVE_CLOCK_MONO static int use_monotonic = 0; /* monotonic clock runtime availability flag */ static struct timeval tv_diff; /* system time - monotonic difference at start */ #endif static unsigned int hash(struct timer *t) { /* We can hash on the trigger time, even though it can change over ** the life of a timer via either the periodic bit or the tmr_reset() ** call. This is because both of those guys call l_resort(), which ** recomputes the hash and moves the timer to the appropriate list. */ return ((unsigned int)t->time.tv_sec ^ (unsigned int)t->time.tv_usec) % HASH_SIZE; } static void l_add(struct timer *t) { int h = t->hash; struct timer *t2; struct timer *t2prev; t2 = timers[h]; if (!t2) { /* The list is empty. */ timers[h] = t; t->prev = t->next = NULL; } else { if ( t->time.tv_sec < t2->time.tv_sec || (t->time.tv_sec == t2->time.tv_sec && t->time.tv_usec <= t2->time.tv_usec)) { /* The new timer goes at the head of the list. */ timers[h] = t; t->prev = NULL; t->next = t2; t2->prev = t; } else { /* Walk the list to find the insertion point. */ for (t2prev = t2, t2 = t2->next; t2; t2prev = t2, t2 = t2->next) { if (t->time.tv_sec < t2->time.tv_sec || (t->time.tv_sec == t2->time.tv_sec && t->time.tv_usec <= t2->time.tv_usec)) { /* Found it. */ t2prev->next = t; t->prev = t2prev; t->next = t2; t2->prev = t; return; } } /* Oops, got to the end of the list. Add to tail. */ t2prev->next = t; t->prev = t2prev; t->next = NULL; } } } static void l_remove(struct timer *t) { if (!t) return; if (!t->prev) timers[t->hash] = t->next; else t->prev->next = t->next; if (t->next) t->next->prev = t->prev; } static void l_resort(struct timer *t) { /* Remove the timer from its old list. */ l_remove(t); /* Recompute the hash. */ t->hash = hash(t); /* And add it back in to its new list, sorted correctly. */ l_add(t); } void tmr_init(void) { int h; for (h = 0; h < HASH_SIZE; ++h) timers[h] = NULL; free_timers = NULL; alloc_count = active_count = free_count = 0; /* Check for monotonic clock availability */ #ifdef HAVE_CLOCK_MONO struct timespec ts; struct timeval tv_start, tv; /* Try to get monotonic clock time */ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { use_monotonic = 1; /* Get current system time */ gettimeofday(&tv_start, NULL); tv.tv_sec = ts.tv_sec; tv.tv_usec = ts.tv_nsec / 1000L; /* Calculate and save the difference: tv_start is since ** the Epoch, so tv_start > ts tv_diff = tv_start - tv */ timersub(&tv_start, &tv, &tv_diff); } #endif } struct timer *tmr_create(struct timeval *now, void (*cb)(arg_t, struct timeval *), arg_t arg, long msecs, int periodic) { struct timer *t; if (free_timers) { t = free_timers; free_timers = t->next; --free_count; } else { t = malloc(sizeof(struct timer)); if (!t) return NULL; ++alloc_count; } t->cb = cb; t->arg = arg; t->msecs = msecs; t->periodic = periodic; if (now) t->time = *now; else tmr_prepare_timeval(&t->time); t->time.tv_sec += msecs / 1000L; t->time.tv_usec += (msecs % 1000L) * 1000L; if (t->time.tv_usec >= 1000000L) { t->time.tv_sec += t->time.tv_usec / 1000000L; t->time.tv_usec %= 1000000L; } t->hash = hash(t); /* Add the new timer to the proper active list. */ l_add(t); ++active_count; return t; } struct timeval *tmr_timeout(struct timeval *now) { long msecs; static struct timeval timeout; msecs = tmr_mstimeout(now); if (msecs == INFTIM) return NULL; timeout.tv_sec = msecs / 1000L; timeout.tv_usec = (msecs % 1000L) * 1000L; return &timeout; } long tmr_mstimeout(struct timeval *now) { long msecs, m; int gotone; int h; gotone = 0; msecs = 0; /* Since the lists are sorted, we only need to look at ** the first timer on each one. */ for (h = 0; h < HASH_SIZE; ++h) { struct timer *t; t = timers[h]; if (!t) continue; m = (t->time.tv_sec - now->tv_sec) * 1000L + (t->time.tv_usec - now->tv_usec) / 1000L; if (!gotone) { msecs = m; gotone = 1; } else if (m < msecs) { msecs = m; } } if (!gotone) return INFTIM; if (msecs <= 0) msecs = 500; /* Was 0, but we should never poll() < 500 msec */ return msecs; } void tmr_run(struct timeval *now) { int h; struct timer *t; struct timer *next; for (h = 0; h < HASH_SIZE; ++h) for (t = timers[h]; t; t = next) { next = t->next; /* Since the lists are sorted, as soon as we find a timer ** that isn't ready yet, we can go on to the next list. */ if (t->time.tv_sec > now->tv_sec || (t->time.tv_sec == now->tv_sec && t->time.tv_usec > now->tv_usec)) break; (t->cb) (t->arg, now); if (t->periodic) { /* Reschedule. */ t->time.tv_sec += t->msecs / 1000L; t->time.tv_usec += (t->msecs % 1000L) * 1000L; if (t->time.tv_usec >= 1000000L) { t->time.tv_sec += t->time.tv_usec / 1000000L; t->time.tv_usec %= 1000000L; } l_resort(t); } else tmr_cancel(t); } } void tmr_reset(struct timeval *now, struct timer *t) { if (!t) return; t->time = *now; t->time.tv_sec += t->msecs / 1000L; t->time.tv_usec += (t->msecs % 1000L) * 1000L; if (t->time.tv_usec >= 1000000L) { t->time.tv_sec += t->time.tv_usec / 1000000L; t->time.tv_usec %= 1000000L; } l_resort(t); } void tmr_cancel(struct timer *t) { if (!t) return; /* Remove it from its active list. */ l_remove(t); --active_count; /* And put it on the free list. */ t->prev = NULL; t->next = free_timers; free_timers = t; ++free_count; } void tmr_cleanup(void) { struct timer *t; while (free_timers) { t = free_timers; free_timers = t->next; --free_count; --alloc_count; free(t); } } void tmr_destroy(void) { int h; for (h = 0; h < HASH_SIZE; ++h) { while (timers[h]) tmr_cancel(timers[h]); } tmr_cleanup(); } /* Generate debugging statistics syslog message. */ void tmr_logstats(long secs) { syslog(LOG_INFO, " timers - %d allocated, %d active, %d free", alloc_count, active_count, free_count); if (active_count + free_count != alloc_count) syslog(LOG_ERR, "timer counts don't add up!"); } /* Fill timeval structure for further usage by the package. */ void tmr_prepare_timeval(struct timeval *tv) { #ifdef HAVE_CLOCK_MONO struct timespec ts; struct timeval tv0; if (use_monotonic) { /* use monotonic clock source ? */ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) { perror("clock_gettime"); return; } tv0.tv_sec = ts.tv_sec; tv0.tv_usec = ts.tv_nsec / 1000L; /* Return system time value like it was running accurately */ timeradd(&tv_diff, &tv0, tv); } else { #endif gettimeofday(tv, NULL); #ifdef HAVE_CLOCK_MONO } #endif } merecat-2.31+git20220513+ds/src/timers.h000066400000000000000000000073151424066547300173430ustar00rootroot00000000000000/* timers.h - header file for timers package ** ** Copyright (C) 1995-2015 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _TIMERS_H_ #define _TIMERS_H_ #include #include #ifndef INFTIM #define INFTIM -1 #endif /* arg_t is a random value that tags along with a timer. The client ** can use it for whatever, and it gets passed to the callback when the ** timer triggers. */ typedef union { void *p; int i; long l; } arg_t; extern arg_t noarg; /* for use when you don't care */ struct timer { struct timer *prev; struct timer *next; int hash; struct timeval time; long msecs; int periodic; void (*cb)(arg_t, struct timeval *); arg_t arg; }; /* Initialize the timer package. */ extern void tmr_init(void); /* Set up a timer, either periodic or one-shot. Returns NULL on errors. */ extern struct timer *tmr_create(struct timeval *now, void (*cb)(arg_t, struct timeval *), arg_t arg, long msecs, int periodic); /* Returns a timeout indicating how long until the next timer triggers. You ** can just put the call to this routine right in your select(). Returns ** (struct timeval*) 0 if no timers are pending. */ extern struct timeval *tmr_timeout(struct timeval *now); /* Returns a timeout in milliseconds indicating how long until the next timer ** triggers. You can just put the call to this routine right in your poll(). ** Returns INFTIM (-1) if no timers are pending. */ extern long tmr_mstimeout(struct timeval *now); /* Run the list of timers. Your main program needs to call this every so often, ** or as indicated by tmr_timeout(). */ extern void tmr_run(struct timeval *now); /* Reset the clock on a timer, to current time plus the original timeout. */ extern void tmr_reset(struct timeval *now, struct timer *timer); /* Deschedule a timer. Note that non-periodic timers are automatically ** descheduled when they run, so you don't have to call this on them. */ extern void tmr_cancel(struct timer *timer); /* Clean up the timers package, freeing any unused storage. */ extern void tmr_cleanup(void); /* Cancel all timers and free storage, usually in preparation for exitting. */ extern void tmr_destroy(void); /* Generate debugging statistics syslog message. */ extern void tmr_logstats(long secs); /* Fill timeval structure for further usage by the package. */ extern void tmr_prepare_timeval(struct timeval *tv); #endif /* _TIMERS_H_ */ merecat-2.31+git20220513+ds/tests/000077500000000000000000000000001424066547300162345ustar00rootroot00000000000000merecat-2.31+git20220513+ds/tests/.gitignore000066400000000000000000000000231424066547300202170ustar00rootroot00000000000000.deps/ *.log *.trs merecat-2.31+git20220513+ds/tests/Makefile.am000066400000000000000000000005601424066547300202710ustar00rootroot00000000000000EXTRA_DIST = merecat.conf start.sh stop.sh EXTRA_DIST += cgi.sh gzip.sh redirect.sh location.sh php.sh CLEANFILES = *~ *.trs *.log TEST_EXTENSIONS = .sh TESTS = start.sh TESTS += cgi.sh TESTS += php.sh TESTS += gzip.sh TESTS += redirect.sh TESTS += location.sh TESTS += stop.sh merecat-2.31+git20220513+ds/tests/cgi.sh000077500000000000000000000001541424066547300173350ustar00rootroot00000000000000#!/bin/sh set -ex curl http://localhost:8086/cgi-bin/printenv 2>/dev/null |grep 'SERVER_SOFTWARE=merecat/' merecat-2.31+git20220513+ds/tests/gzip.sh000077500000000000000000000002361424066547300175450ustar00rootroot00000000000000#!/bin/sh # https://en.wikipedia.org/wiki/HTTP_compression set -ex curl -H "Accept-Encoding: gzip" -I http://localhost:8086/main.css 2>/dev/null |grep gzip merecat-2.31+git20220513+ds/tests/location.sh000077500000000000000000000005221424066547300204020ustar00rootroot00000000000000#!/bin/sh # Verify location directive set -ex # Fetch from 8080:/.secret/path echo "Pass 1/2" curl -s -I http://localhost:8080/.secret/path/merecat.jpg | tee foo |grep "200 OK" cat foo; rm foo # But not from :8086 echo "Pass 2/2" curl -s -I http://localhost:8086/.secret/path/merecat.jpg |tee foo | grep "404 Not Found" cat foo; rm foo merecat-2.31+git20220513+ds/tests/merecat.conf000066400000000000000000000005621424066547300205260ustar00rootroot00000000000000cgi "**.cgi|/cgi-bin/*" { enabled = true } php "**.php*" { enabled = true } server hej { port = 8086 } server nej { port = 8080 # If location is requested, replace with path location "/.secret/path/**" { path = "/img" } redirect "/**" { code = 301 location = "http://$host:8086$request_uri$args" } } merecat-2.31+git20220513+ds/tests/php.sh000077500000000000000000000003251424066547300173620ustar00rootroot00000000000000#!/bin/sh set -ex echo "" >srv/test.php ls srv cat srv/test.php curl http://localhost:8086/test.php?name=foobar 2>/dev/null |grep 'Hello foobar' merecat-2.31+git20220513+ds/tests/redirect.sh000077500000000000000000000002131424066547300203700ustar00rootroot00000000000000#!/bin/sh # https://en.wikipedia.org/wiki/URL_redirection curl -Ls -w %{url_effective} http://127.0.0.1:8080 |grep http://127.0.0.1:8086/ merecat-2.31+git20220513+ds/tests/start.sh000077500000000000000000000007011424066547300177260ustar00rootroot00000000000000#!/bin/sh -e srvfiles="main.css index.html img/merecat.jpg" if [ -z "$srcdir" ]; then srcdir=. fi mkdir -p srv/img srv/cgi-bin for file in $srvfiles; do cp ${srcdir}/../www/$file srv/$file gzip -c srv/$file > srv/$file.gz done cp ${srcdir}/../www/cgi-bin/printenv srv/cgi-bin/ echo "Starting merecat httpd, config file ${srcdir}/merecat.conf" ../src/merecat -f ${srcdir}/merecat.conf -n -l debug srv & echo $! >merecat.pid sleep 2 merecat-2.31+git20220513+ds/tests/stop.sh000077500000000000000000000001041424066547300175530ustar00rootroot00000000000000#!/bin/sh kill `cat merecat.pid` sleep 1 rm -rf srv rm merecat.pid merecat-2.31+git20220513+ds/throttle.conf000066400000000000000000000004771424066547300176160ustar00rootroot00000000000000# example throttle file # # pattern [M-]N # # maximum of N bytes per second # minimum of M bytes per second # # Definint a minimum means we don't start new connections # if we're doing less than M bytes/s ** 400 **.jpg|**.gif 1000-50000 jef/** 20000 merecat-2.31+git20220513+ds/www/000077500000000000000000000000001424066547300157165ustar00rootroot00000000000000merecat-2.31+git20220513+ds/www/.gitignore000066400000000000000000000000031424066547300176770ustar00rootroot00000000000000*~ merecat-2.31+git20220513+ds/www/Makefile.am000066400000000000000000000003301424066547300177460ustar00rootroot00000000000000wwwdir = $(WEBDIR) SUBDIRS = cgi-bin icons img dist_www_DATA = index.html main.css phpinfo.php test.php dist_www_DATA += htpasswd.1.html merecat.conf.5.html merecat.8.html ssi.8.html man: ../man.sh merecat-2.31+git20220513+ds/www/cgi-bin/000077500000000000000000000000001424066547300172265ustar00rootroot00000000000000merecat-2.31+git20220513+ds/www/cgi-bin/.gitignore000066400000000000000000000000121424066547300212070ustar00rootroot00000000000000.deps ssi merecat-2.31+git20220513+ds/www/cgi-bin/Makefile.am000066400000000000000000000004241424066547300212620ustar00rootroot00000000000000AUTOMAKE_OPTIONS = subdir-objects no-dependencies cgidir = $(WEBDIR)/cgi-bin cgi_PROGRAMS = ssi dist_cgi_SCRIPTS = printenv ssi_SOURCES = ssi.c ssi_CPPFLAGS = -I$(top_srcdir)/src ssi_LDADD = ../../src/libmatch.a $(LIBOBJS) merecat-2.31+git20220513+ds/www/cgi-bin/printenv000077500000000000000000000005261424066547300210240ustar00rootroot00000000000000#!/bin/sh date=`date -u '+%a, %d %b %Y %H:%M:%S %Z'` cat << EOF Content-type: text/plain Expires: $date CGI printenv EOF echo 'Date:' date echo echo 'Id:' id echo echo 'Env:' printenv echo if [ "$CONTENT_LENGTH" != "" ] ; then if [ "$CONTENT_LENGTH" -ne 0 ] ; then echo 'Input:' echo dd bs=1 count=$CONTENT_LENGTH echo fi fi merecat-2.31+git20220513+ds/www/cgi-bin/ssi.c000066400000000000000000000431721424066547300201770ustar00rootroot00000000000000/* ssi - server-side-includes CGI program ** ** Copyright (C) 1995 Jef Poskanzer ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNERS OR CONTRIBUTORS BE ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ** THE POSSIBILITY OF SUCH DAMAGE. */ #include /* System headers */ #include #include #include #include #include #include #include #include /* Local headers */ #include "libhttpd.h" #include "merecat.h" #include "match.h" #define ST_GROUND 0 #define ST_LESSTHAN 1 #define ST_BANG 2 #define ST_MINUS1 3 #define ST_MINUS2 4 #define ERRMSG_DEFAULT "[an error occurred while processing this directive]" static char *url; static char *errmsg = NULL; static char timefmt[100]; static int sizefmt; #define SF_BYTES 0 #define SF_ABBREV 1 static struct stat sb; static void read_file(char *vfilename, char *filename, FILE *fp); static void send_response(char *title, char *fmt, ...) { va_list ap; char *srv, *host, *port; printf("\n" "\n" " \n" " %s\n" " \n" "%s" " \n" " \n" "
\n" "

%s

\n" "

\n", title, httpd_css_default(), title); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); host = getenv("SERVER_NAME"); port = getenv("SERVER_PORT"); srv = getenv("SERVER_SOFTWARE"); printf("

\n" "
%s httpd at %s port %s
" "
\n" "\n", srv, host, port); } static void internal_error(char *reason) { char *title = "500 Internal Error"; send_response(title, "Something unusual went wrong in a server-side " "includes request:\n" "
\n" "%s\n" "
\n", reason); } static void not_found(char *filename) { char *title = "404 Not Found"; send_response(title, "The requested server-side includes filename, %s,\n" "does not seem to exist.", filename); } static void show_errmsg(void) { if (!errmsg) return; fputs(errmsg, stdout); } static void not_found2(char *directive, char *tag, char *filename2) { syslog(LOG_NOTICE, "The filename requested in a %s %s directive; %s, " "does not seem to exist.", directive, tag, filename2); show_errmsg(); } static void not_permitted(char *directive, char *tag, char *val) { syslog(LOG_NOTICE, "The filename requested in the %s %s=%s directive, " "is not allowed.", directive, tag, val); show_errmsg(); } static void unknown_directive(char *filename, char *directive) { syslog(LOG_NOTICE, "The requested server-side-includes filename, %s, " "tried to use an unknown directive, %s.", filename, directive); show_errmsg(); } static void unknown_tag(char *filename, char *directive, char *tag) { syslog(LOG_NOTICE, "The requested server-side-includes filename, %s, " "tried to use directive %s with an unknown tag, %s.", filename, directive, tag); show_errmsg(); } static void unknown_value(char *filename, char *directive, char *tag, char *val) { syslog(LOG_NOTICE, "The requested server-side-includes filename, %s, " "tried to use directive %s %s with an unknown value, %s.", filename, directive, tag, val); show_errmsg(); } /* Used for the various commands that accept a file name. ** These commands accept two tags: ** virtual ** Gives a virtual path to a document on the server. ** file ** Gives a pathname relative to the current directory. ../ cannot ** be used in this pathname, nor can absolute paths be used. */ static int get_filename(char *vfilename, char *filename, char *directive, char *tag, char *val, char *fn, int fnsize) { char *cp; int vl, fl; memset(fn, 0, fnsize); vl = strlen(vfilename); fl = strlen(filename); if (strcmp(tag, "virtual") == 0) { if (strstr(val, "../")) { not_permitted(directive, tag, val); return -1; } /* Figure out root using difference between vfilename and filename. */ if (vl > fl || strcmp(vfilename, &filename[fl - vl]) != 0) return -1; if (fl - vl + strlen(val) >= fnsize) return -1; memcpy(fn, filename, fl - vl); strlcat(fn, "/", fnsize); } else if (strcmp(tag, "file") == 0) { if (val[0] == '/' || strstr(val, "../")) { not_permitted(directive, tag, val); return -1; } if (fl + 1 + strlen(val) >= fnsize) return -1; strlcpy(fn, filename, fnsize); cp = strrchr(fn, '/'); if (!cp) strlcat(fn, "/", fnsize); else *++cp = 0; } else { unknown_tag(filename, directive, tag); return -1; } strlcat(fn, val, fnsize); return 0; } static int check_filename(char *filename) { static char *cgi_pattern; static int inited = 0; #ifdef AUTH_FILE struct stat sb; size_t len; char *authname; char *dirname; char *cp; int fnl; int r; #endif if (!inited) { /* Get the cgi pattern. */ cgi_pattern = getenv("CGI_PATTERN"); #ifdef CGI_PATTERN if (!cgi_pattern) cgi_pattern = CGI_PATTERN; #endif inited = 1; } /* ../ is not permitted. */ if (strstr(filename, "../")) return 0; #ifdef AUTH_FILE /* Ensure that we are not reading a basic auth password file. */ fnl = strlen(filename); len = fnl - sizeof(AUTH_FILE); if (strcmp(filename, AUTH_FILE) == 0 || (fnl >= sizeof(AUTH_FILE) && strcmp(&filename[len + 1], AUTH_FILE) == 0 && filename[len] == '/')) return 0; /* Check for an auth file in the same directory. We can't do an actual ** auth password check here because CGI programs are not given the ** authorization header, for security reasons. So instead we just ** prohibit access to all auth-protected files. */ dirname = strdup(filename); if (!dirname) return 0; /* out of memory */ cp = strrchr(dirname, '/'); if (!cp) strcpy(dirname, "."); else *cp = '\0'; len = strlen(dirname) + 1 + sizeof(AUTH_FILE); authname = malloc(len); if (!authname) { free(dirname); return 0; /* out of memory */ } snprintf(authname, len, "%s/%s", dirname, AUTH_FILE); r = stat(authname, &sb); free(dirname); free(authname); if (r == 0) return 0; #endif /* AUTH_FILE */ /* Ensure that we are not reading a CGI file. */ if (cgi_pattern && match(cgi_pattern, filename)) return 0; return 1; } static void show_time(time_t t, int gmt) { struct tm *tm; char tbuf[500]; if (gmt) tm = gmtime(&t); else tm = localtime(&t); if (strftime(tbuf, sizeof(tbuf), timefmt, tm) > 0) fputs(tbuf, stdout); } static void show_size(off_t size) { switch (sizefmt) { case SF_BYTES: printf("%ld", (long)size); /* spec says should have commas */ break; case SF_ABBREV: if (size < 1024) printf("%ld", (long)size); else if (size < 1024 * 1024) printf("%ldK", (long)size / 1024L); else if (size < 1024 * 1024 * 1024) printf("%ldM", (long)size / (1024L * 1024L)); else printf("%ldG", (long)size / (1024L * 1024L * 1024L)); break; } } /* The config directive controls various aspects of the file parsing. ** There are two valid tags: ** timefmt ** Gives the server a new format to use when providing dates. This ** is a string compatible with the strftime library call. ** sizefmt ** Determines the formatting to be used when displaying the size of ** a file. Valid choices are bytes, for a formatted byte count ** (formatted as 1,234,567), or abbrev for an abbreviated version ** displaying the number of kilobytes or megabytes the file occupies. */ static void do_config(char *vfilename, char *filename, FILE *fp, char *directive, char *tag, char *val) { if (strcmp(tag, "timefmt") == 0) { strlcpy(timefmt, val, sizeof(timefmt)); } else if (strcmp(tag, "sizefmt") == 0) { if (strcmp(val, "bytes") == 0) sizefmt = SF_BYTES; else if (strcmp(val, "abbrev") == 0) sizefmt = SF_ABBREV; else unknown_value(filename, directive, tag, val); } else if (strcmp(tag, "errmsg") == 0) { free(errmsg); errmsg = strdup(val); } else unknown_tag(filename, directive, tag); } /* Inserts the text of another document into the parsed document. */ static void do_include(char *vfilename, char *filename, FILE *fp, char *directive, char *tag, char *val) { char vfilename2[1000]; char filename2[1000]; FILE *fp2; if (get_filename(vfilename, filename, directive, tag, val, filename2, sizeof(filename2)) < 0) return; if (!check_filename(filename2)) { not_permitted(directive, tag, filename2); return; } fp2 = fopen(filename2, "r"); if (!fp2) { not_found2(directive, tag, filename2); return; } if (strcmp(tag, "virtual") == 0) { if (strlen(val) < sizeof(vfilename2)) strlcpy(vfilename2, val, sizeof(vfilename2)); else strlcpy(vfilename2, filename2, sizeof(vfilename2)); } else { if (strlen(vfilename) + 1 + strlen(val) < sizeof(vfilename2)) { strlcpy(vfilename2, vfilename, sizeof(vfilename2)); if (!strrchr(vfilename2, '/')) strlcat(vfilename2, "/", sizeof(vfilename2)); strlcat(vfilename2, val, sizeof(vfilename2)); } else strlcpy(vfilename2, filename2, sizeof(vfilename2)); } read_file(vfilename2, filename2, fp2); fclose(fp2); } /* Prints the value of one of the include variables. Any dates are ** printed subject to the currently configured timefmt. The only valid ** tag is var, whose value is the name of the variable you wish to echo. */ static void do_echo(char *vfilename, char *filename, FILE *fp, char *directive, char *tag, char *val) { char *cp; time_t t; if (strcmp(tag, "var") != 0) unknown_tag(filename, directive, tag); else { if (strcmp(val, "DOCUMENT_NAME") == 0) { /* The current filename. */ cp = strrchr(vfilename, '/'); if (cp) fputs(++cp, stdout); else fputs(vfilename, stdout); } else if (strcmp(val, "DOCUMENT_URI") == 0) { /* The virtual path to this file (such as /~robm/foo.shtml). */ fputs(vfilename, stdout); } else if (strcmp(val, "QUERY_STRING_UNESCAPED") == 0) { /* The unescaped version of any search query the client sent. */ cp = getenv("QUERY_STRING"); if (cp) fputs(cp, stdout); } else if (strcmp(val, "DATE_LOCAL") == 0) { /* The current date, local time zone. */ t = time(NULL); show_time(t, 0); } else if (strcmp(val, "DATE_GMT") == 0) { /* Same as DATE_LOCAL but in Greenwich mean time. */ t = time(NULL); show_time(t, 1); } else if (strcmp(val, "LAST_MODIFIED") == 0) { /* The last modification date of the current document. */ if (fstat(fileno(fp), &sb) >= 0) show_time(sb.st_mtime, 0); } else { /* Try an environment variable. */ cp = getenv(val); if (!cp) unknown_value(filename, directive, tag, val); else fputs(cp, stdout); } } } /* Prints the size of the specified file. */ static void do_fsize(char *vfilename, char *filename, FILE *fp, char *directive, char *tag, char *val) { char filename2[1000]; if (get_filename(vfilename, filename, directive, tag, val, filename2, sizeof(filename2)) < 0) return; if (stat(filename2, &sb) < 0) { not_found2(directive, tag, filename2); return; } show_size(sb.st_size); } /* Prints the last modification date of the specified file. */ static void do_flastmod(char *vfilename, char *filename, FILE *fp, char *directive, char *tag, char *val) { char filename2[1000]; if (get_filename(vfilename, filename, directive, tag, val, filename2, sizeof(filename2)) < 0) return; if (stat(filename2, &sb) < 0) { not_found2(directive, tag, filename2); return; } show_time(sb.st_mtime, 0); } static void parse(char *vfilename, char *filename, FILE *fp, char *str) { char *directive; char *cp; int ntags; char *tags[200]; int dirn; #define DI_CONFIG 0 #define DI_INCLUDE 1 #define DI_ECHO 2 #define DI_FSIZE 3 #define DI_FLASTMOD 4 int i; directive = str; directive += strspn(directive, " \t\n\r"); ntags = 0; cp = directive; for (;;) { cp = strpbrk(cp, " \t\n\r\""); if (!cp) break; if (*cp == '"') { cp = strpbrk(cp + 1, "\""); ++cp; if (*cp == '\0') break; } *cp++ = '\0'; cp += strspn(cp, " \t\n\r"); if (*cp == '\0') break; if (ntags < sizeof(tags) / sizeof(*tags)) tags[ntags++] = cp; } if (strcmp(directive, "config") == 0) dirn = DI_CONFIG; else if (strcmp(directive, "include") == 0) dirn = DI_INCLUDE; else if (strcmp(directive, "echo") == 0) dirn = DI_ECHO; else if (strcmp(directive, "fsize") == 0) dirn = DI_FSIZE; else if (strcmp(directive, "flastmod") == 0) dirn = DI_FLASTMOD; else { unknown_directive(filename, directive); return; } for (i = 0; i < ntags; ++i) { char *val; if (i > 0) putchar(' '); val = strchr(tags[i], '='); if (!val) val = ""; else *val++ = '\0'; if (*val == '"' && val[strlen(val) - 1] == '"') { val[strlen(val) - 1] = '\0'; ++val; } switch (dirn) { case DI_CONFIG: do_config(vfilename, filename, fp, directive, tags[i], val); break; case DI_INCLUDE: do_include(vfilename, filename, fp, directive, tags[i], val); break; case DI_ECHO: do_echo(vfilename, filename, fp, directive, tags[i], val); break; case DI_FSIZE: do_fsize(vfilename, filename, fp, directive, tags[i], val); break; case DI_FLASTMOD: do_flastmod(vfilename, filename, fp, directive, tags[i], val); break; } } } /* Now slurp in the rest of the comment from the input file. */ static void slurp(char *vfilename, char *filename, FILE *fp) { char buf[1000]; int i; int state; int ich; i = 0; state = ST_GROUND; while ((ich = getc(fp)) != EOF) { switch (state) { case ST_GROUND: if (ich == '-') state = ST_MINUS1; break; case ST_MINUS1: if (ich == '-') state = ST_MINUS2; else state = ST_GROUND; break; case ST_MINUS2: if (ich == '>') { buf[i - 2] = '\0'; parse(vfilename, filename, fp, buf); return; } else if (ich != '-') state = ST_GROUND; break; } if (i < sizeof(buf) - 1) buf[i++] = (char)ich; } } /* Copy it to output, while running a state-machine to look for ** SSI directives. */ static void read_file(char *vfilename, char *filename, FILE *fp) { int ich; int state; state = ST_GROUND; while ((ich = getc(fp)) != EOF) { switch (state) { case ST_GROUND: if (ich == '<') { state = ST_LESSTHAN; continue; } break; case ST_LESSTHAN: if (ich == '!') { state = ST_BANG; continue; } else { state = ST_GROUND; putchar('<'); } break; case ST_BANG: if (ich == '-') { state = ST_MINUS1; continue; } else { state = ST_GROUND; fputs("
merecat httpd is free software under the 2-clause BSD license.
merecat-2.31+git20220513+ds/www/header.html000066400000000000000000000004671424066547300200430ustar00rootroot00000000000000 %TITLE%
merecat-2.31+git20220513+ds/www/htpasswd.1.html000066400000000000000000000102631424066547300206020ustar00rootroot00000000000000 htpasswd.1
htpasswd(1) General Commands Manual htpasswd(1)

htpasswd
Create and update user authentication files

htpasswd [
-cm
] FILE USERNAME

htpasswd is used to create and update the flat-files used to store usernames and password for basic authentication of HTTP users. Resources available from the Merecat web server can be restricted to just the users listed in the files created by htpasswd. This program can only be used when the usernames are stored in a flat-file.
This manual page only lists the command line arguments for htpasswd. For details of the directives necessary to enable user authentication in the web server, see merecat(8), the Merecat httpd README, or the GitHub project home page ⟨https://github.com/troglobit/merecat⟩.

Create, or recreate the .htpwassd file, FILE, if it already exists.
Use MD5 encryption for passwords, this is tested at runtime. So this option is simply for compatibility with other similar tools.
FILE
Name of the file to contain the user name and password, usually .htpasswd. If -c is given, this file is created if it does not already exist, or deleted and recreated if it does exist.
USERNAME
The username to create or update in passwdfile, FILE. If username does not exist is this file, an entry is added. If it does exist, the password is changed.

merecat(8)

Rob McCool ⟨robm@stanford.edu⟩ originally wrote htpasswd for NCSA httpd. It then (naturally) made its way to Apache and other web servers, like Roxen Challenger.
Jef Poskanzer ⟨jef@mail.acme.com⟩ modified it 29aug97 to accept new password on stdin, if stdin is a pipe or file. This is necessary for use from CGI.
August 3, 2019 merecat (2.32)
merecat-2.31+git20220513+ds/www/icons/000077500000000000000000000000001424066547300170315ustar00rootroot00000000000000merecat-2.31+git20220513+ds/www/icons/Makefile.am000066400000000000000000000005021424066547300210620ustar00rootroot00000000000000wwwiconsdir = $(WEBDIR)/icons if HAVE_ICONS dist_wwwicons_DATA = back.gif blank.gif folder.gif generic.gif movie.gif \ unknown.gif binary.gif comp.gray.gif folder.open.gif \ image.gif text.gif uuencoded.gif binhex.gif compressed.gif \ forward.gif index.gif transfer.gif favicon.ico endif merecat-2.31+git20220513+ds/www/icons/back.gif000066400000000000000000000003301424066547300204140ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™fff333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,KēÜū#ōj3SģĸÎÜõiœ7Nv‚t7*EnXm/û’ë5FPņęxƤ‘Ļl(N§ ÚŒĀ¨:‚@ÕV’ŪĐUüč‘Ī‹;merecat-2.31+git20220513+ds/www/icons/binary.gif000066400000000000000000000003661424066547300210110ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙ĖĖĖ™™™333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,iHēŧņ0@Ģ$žY ]!Šq^¨Ĩ ĄkęĩŽßjAčüvƒQhčŗ)ƒä āŠn%Ķ–“—ž*ԂĨZUÆîÄ)ô„§´ã9ë!_Ņ8ëÚŗës˜;¯î‹ J‚I;C‡ˆS ;merecat-2.31+git20220513+ds/www/icons/binhex.gif000066400000000000000000000003661424066547300210020ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™fff333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,iHēŧņ0@Ģ$žYēīĀjÜį…U‰˜^+`žQߛ<ē™[@Š`((äŠ&…h”ôLÁbޤ.›˜]Ģ|”@Š s;"›ĩ".ÍļŋÖÚ<ÕŌhŠî,—ÛD‚ƒC6;ˆM ;merecat-2.31+git20220513+ds/www/icons/blank.gif000066400000000000000000000002241424066547300206050ustar00rootroot00000000000000GIF89aĄ˙˙˙Ė˙˙!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,ŒŠËíŖœ´Ú‹ŗŪŧû†I;merecat-2.31+git20220513+ds/www/icons/comp.gray.gif000066400000000000000000000003661424066547300214240ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™fĖ˙333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,iēÜū0ĘXĒŊĀ왺aHˆ 7Ŧ+aĸËēÅ،ōüŪ`ŪîŒaH›Ī§Æ ’0ÄA@­Z¯„Đ´Číj¯`kV1-ĖΊ`Ŧ0+?įPĀŊ]Ûˇ€ø\¨ûīy5iƒ']‡\ ;merecat-2.31+git20220513+ds/www/icons/compressed.gif000066400000000000000000000020161424066547300216630ustar00rootroot00000000000000GIF89aį˙˙˙˙˙Ė˙˙™˙˙f˙˙3˙˙˙Ė˙˙ĖĖ˙Ė™˙Ėf˙Ė3˙Ė˙™˙˙™Ė˙™™˙™f˙™3˙™˙f˙˙fĖ˙f™˙ff˙f3˙f˙3˙˙3Ė˙3™˙3f˙33˙3˙˙˙Ė˙™˙f˙3˙Ė˙˙Ė˙ĖĖ˙™Ė˙fĖ˙3Ė˙ĖĖ˙ĖĖĖĖĖ™ĖĖfĖĖ3ĖĖĖ™˙Ė™ĖĖ™™Ė™fĖ™3Ė™Ėf˙ĖfĖĖf™ĖffĖf3ĖfĖ3˙Ė3ĖĖ3™Ė3fĖ33Ė3Ė˙ĖĖĖ™ĖfĖ3Ė™˙˙™˙Ė™˙™™˙f™˙3™˙™Ė˙™Ė˙˙™Ėf™Ė3™Ė™™˙™™Ė™™™™™f™™3™™™f˙™fĖ™f™™ff™f3™f™3˙™3Ė™3™™3f™33™3™˙™Ė™™™f™3™f˙˙f˙Ėf˙™f˙ff˙3f˙fĖ˙fĖĖfĖ™fĖffĖ3fĖf™˙f™Ėf™™f™ff™3f™ff˙ffĖff™fffff3fff3˙f3Ėf3™f3ff33f3f˙fĖf™fff3f3˙˙3˙Ė3˙™3˙f3˙33˙3Ė˙3ĖĖ3Ė™3Ėf3Ė33Ė3™˙3™Ė3™™3™f3™33™3f˙3fĖ3f™3ff3f33f33˙33Ė33™33f333333˙3Ė3™3f333˙˙˙Ė˙™˙f˙3˙Ė˙ĖĖĖ™ĖfĖ3Ė™˙™Ė™™™f™3™f˙fĖf™fff3f3˙3Ė3™3f333˙Ė™f3îŨģLjwUD"îŨģLjwUD"îŨģLjwUD"îîîŨŨŨģģģĒĒLjˆˆwwwUUUDDD"""!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų$,™I$ÁĒ`ÁdCV #lČСĄ+Zŧ(0cCŠ'øĮФIŽ˙Š\ųåGVbZi™đ‡Ž/aƤÉá‘@›75žœI‚CΟF9ũ83ŠO@ælh%éQ¨V9LÍú4jĪŠ^ģ: 3ĒØžKŠū*ÖgZ˜-mv}ú–(Ô§ëŌ”¨%+™2K†$Á’Ĩ€;merecat-2.31+git20220513+ds/www/icons/favicon.ico000066400000000000000000000764461424066547300211730ustar00rootroot00000000000000@@ (BF00 ¨%nB  ¨h hžx(@€ @  3Tr”´ĪåôüûķäÎŗ“rS2'HvĄÆäōúũ˙˙˙˙˙˙˙˙ũúōãÅ uG&=m¤Đíû˙ ˙˙!!!˙+++˙555˙>>>˙DDD˙GGG˙GGG˙DDD˙===˙555˙+++˙ ˙˙ ˙˙ûíĪĸl<<~ŧãøũ˙"""˙>>>˙bbb˙~~~˙———˙ĒĒĒ˙ššš˙ÄÄÄ˙ËËË˙ÎÎÎ˙ÍÍÍ˙ĘĘĘ˙ÃÃÃ˙ššš˙ĒĒĒ˙–––˙}}}˙aaa˙>>>˙!!!˙ ˙ũøãē}; 4u¸æųū˙DDD˙vvv˙ŸŸŸ˙ĀĀĀ˙ÚÚÚ˙ččč˙ōōō˙÷÷÷˙úúú˙üüü˙ũũũ˙ũũũ˙ũũũ˙ũũũ˙üüü˙úúú˙÷÷÷˙ņņņ˙ččč˙ÚÚÚ˙ŋŋŋ˙žžž˙uuu˙CCC˙˙ūųåˇt3 WĒáų˙###˙XXX˙–––˙ÅÅÅ˙ååå˙õõõ˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ûûû˙ôôô˙äää˙ÄÄÄ˙•••˙VVV˙"""˙˙øā¨U#Īõũ˙UUU˙§§§˙ŲŲŲ˙ķķķ˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ûûû˙ōōō˙ØØØ˙ĨĨĨ˙SSS˙˙ũôÎ}!; īũ˙???˙˙äää˙úúú˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙úúú˙ããã˙ššš˙>>>˙˙ũîž:F´ķ˙!!!˙kkk˙ÅÅÅ˙õõõ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ÃÃÃ˙iii˙ ˙˙ō˛D BÁø ˙444˙ŒŒŒ˙ŪŪŪ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ûûû˙øøø˙÷÷÷˙øøø˙úúú˙ũũũ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙úúú˙øøø˙÷÷÷˙øøø˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ŨŨŨ˙ŠŠŠ˙333˙˙øŋ@ F°ø ˙===˙ĸĸĸ˙ååå˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙÷÷÷˙ŪŪŪ˙ŋŋŋ˙ĨĨĨ˙ššš˙ŸŸŸ˙˛˛˛˙ÍÍÍ˙ččč˙ųųų˙ūūū˙˙˙˙˙˙˙˙˙üüü˙îîî˙ŌŌŌ˙ŗŗŗ˙ŸŸŸ˙›››˙¤¤¤˙ĀĀĀ˙ááá˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙äää˙   ˙;;;˙˙÷ŽD 9Šņ˙???˙¤¤¤˙ņņņ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ččč˙˛˛˛˙mmm˙GGG˙<<<˙;;;˙;;;˙@@@˙TTT˙~~~˙¸¸¸˙ééé˙üüü˙õõõ˙ÍÍÍ˙˙[[[˙AAA˙;;;˙;;;˙<<<˙GGG˙ooo˙˛˛˛˙đđđ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙đđđ˙ĸĸĸ˙>>>˙˙đ¨7%čū///˙¤¤¤˙ííí˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ëëë˙œœœ˙CCC˙888˙ccc˙ŒŒŒ˙˙–––˙{{{˙RRR˙333˙DDD˙ŒŒŒ˙ŲŲŲ˙ŽŽŽ˙ZZZ˙222˙HHH˙uuu˙•••˙œœœ˙˙bbb˙222˙GGG˙ŦŦŦ˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ėėė˙ŖŖŖ˙...˙ūį‹$rÛü˙ĄĄĄ˙ņņņ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ķķķ˙ĻĻĻ˙===˙III˙­­­˙ååå˙ķķķ˙ööö˙õõõ˙îîî˙ÚÚÚ˙§§§˙VVV˙)))˙GGG˙(((˙777˙ˆˆˆ˙ĐĐĐ˙ėėė˙õõõ˙ööö˙ôôô˙äää˙ĨĨĨ˙FFF˙GGG˙ēēē˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙đđđ˙   ˙˙üÚp Ižų˙www˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ĪĪĪ˙XXX˙BBB˙ŽŽŽ˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ööö˙ĀĀĀ˙DDD˙ ˙˙„„„˙ééé˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ōōō˙žžž˙///˙www˙ããã˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙uuu˙˙øŊH $“í ˙YYY˙ŅŅŅ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ôôô˙™™™˙777˙˙ččč˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙îîî˙žžž˙555˙222˙!!!˙]]]˙ÎÎÎ˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ÖÖÖ˙YYY˙JJJ˙ÄÄÄ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ĐĐĐ˙WWW˙ ˙ė‘# TĐü,,,˙ĒĒĒ˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ččč˙kkk˙@@@˙ĩĩĩ˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ôôô˙ĩĩĩ˙HHH˙HHH˙ĢĢĢ˙ttt˙111˙vvv˙ŪŪŪ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ėėė˙………˙:::˙¤¤¤˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ôôô˙ŠŠŠ˙,,,˙üĪS  “ō˙kkk˙ŪŪŪ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ßßß˙[[[˙ZZZ˙ÕÕÕ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ĪĪĪ˙fff˙>>>˙˙īīī˙ĶĶĶ˙fff˙;;;˙œœœ˙ííí˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ĻĻĻ˙CCC˙•••˙ōōō˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ŨŨŨ˙jjj˙˙ņ’HÎũ444˙­­­˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ëëë˙˙¨¨¨˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ęęę˙˙888˙|||˙âââ˙ũũũ˙øøø˙ēēē˙III˙TTT˙ÄÄÄ˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ÕÕÕ˙–––˙ŋŋŋ˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ŦŦŦ˙333˙ũÍG{đ ˙eee˙ŲŲŲ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙īīī˙ņņņ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úúú˙ÃÃÃ˙QQQ˙OOO˙ÂÂÂ˙ųųų˙˙˙˙˙ūūū˙ééé˙‰‰‰˙666˙‹‹‹˙ëëë˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙îîî˙õõõ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ŲŲŲ˙ddd˙ ˙đz%°ũ###˙———˙ņņņ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ííí˙ŠŠŠ˙111˙ppp˙ÉÉÉ˙āāā˙âââ˙ßßß˙ÖÖÖ˙ŸŸŸ˙888˙UUU˙ÍÍÍ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙đđđ˙–––˙"""˙ũ¯$FŲ˙DDD˙ĀĀĀ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ßßß˙aaa˙˙)))˙>>>˙DDD˙FFF˙CCC˙===˙///˙˙;;;˙ĩĩĩ˙øøø˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ĀĀĀ˙CCC˙˙ŲFpņ˙iii˙ŨŨŨ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙ŦŦŦ˙xxx˙hhh˙```˙\\\˙ZZZ˙^^^˙ddd˙nnn˙˙ĻĻĻ˙âââ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ÜÜÜ˙hhh˙˙ņo–ú˙‡‡‡˙ėėė˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙õõõ˙ėėė˙įįį˙ããã˙ááá˙āāā˙âââ˙æææ˙ééé˙īīī˙ööö˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ėėė˙†††˙˙ú•ģū%%%˙ŸŸŸ˙õõõ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙žžž˙$$$˙ūēŲ˙...˙¯¯¯˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙¯¯¯˙...˙˙Øī˙666˙ģģģ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ũũũ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ũũũ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ģģģ˙666˙˙īú˙;;;˙ĀĀĀ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙āāā˙ššš˙¤¤¤˙ŽŽŽ˙ÖÖÖ˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ããã˙ŧŧŧ˙ĢĢĢ˙ššš˙ŨŨŨ˙øøø˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ĀĀĀ˙;;;˙˙úų˙:::˙ŋŋŋ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ęęę˙   ˙DDD˙˙˙˙---˙ŒŒŒ˙ááá˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙îîî˙ŠŠŠ˙>>>˙˙˙˙<<<˙’’’˙âââ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ŋŋŋ˙:::˙˙ųė˙555˙ššš˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙îîî˙˙%%%˙˙˙˙˙˙˙ppp˙āāā˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙îîî˙˙$$$˙˙˙˙˙˙˙xxx˙ååå˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ššš˙555˙˙ëÖ˙---˙ŽŽŽ˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ĘĘĘ˙===˙˙˙˙˙666˙===˙˙...˙¨¨¨˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ÄÄÄ˙@@@˙˙˙˙˙333˙555˙˙,,,˙ŗŗŗ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙­­­˙---˙˙Õĩũ###˙›››˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙žžž˙˙˙˙ ˙fff˙ŧŧŧ˙ĘĘĘ˙yyy˙)))˙vvv˙įįį˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ķķķ˙–––˙˙˙˙˙^^^˙ģģģ˙ŊŊŊ˙rrr˙ ˙~~~˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ôôô˙ššš˙"""˙ũŗ‘ų˙‚‚‚˙ęęę˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ŒŒŒ˙ ˙˙˙˙¨¨¨˙÷÷÷˙ũũũ˙ÁÁÁ˙DDD˙fff˙āāā˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙îîî˙ƒƒƒ˙˙˙˙$$$˙ĸĸĸ˙úúú˙ųųų˙ššš˙666˙jjj˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ééé˙˙˙ųiî˙ccc˙ØØØ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙›››˙˙˙˙˙———˙ííí˙õõõ˙°°°˙===˙sss˙æææ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ņņņ˙˙˙˙˙˙”””˙ôôô˙ōōō˙ŦŦŦ˙444˙xxx˙õõõ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙×××˙aaa˙ ˙ífD×˙BBB˙žžž˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ÁÁÁ˙///˙˙˙˙@@@˙ŒŒŒ˙›››˙NNN˙---˙œœœ˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ļļļ˙000˙˙˙˙BBB˙–––˙™™™˙SSS˙(((˙ŖŖŖ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úúú˙ŧŧŧ˙@@@˙˙ÕB"Ŧũ ˙“““˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ėėė˙‡‡‡˙˙˙˙˙˙˙˙ggg˙ŨŨŨ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ééé˙~~~˙˙˙˙˙˙˙˙ggg˙ßßß˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙˙˙ũŠ!tí ˙^^^˙ÕÕÕ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ááá˙ŽŽŽ˙<<<˙˙˙˙+++˙yyy˙ÕÕÕ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙āāā˙‰‰‰˙///˙˙˙˙...˙uuu˙ŅŅŅ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ĶĶĶ˙[[[˙ ˙ėq AČü---˙ĨĨĨ˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙īīī˙ÆÆÆ˙™™™˙………˙˙ļļļ˙ėėė˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ņņņ˙ŧŧŧ˙ŽŽŽ˙~~~˙ŒŒŒ˙ĩĩĩ˙æææ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ķķķ˙ĸĸĸ˙+++˙üÅ>đ˙ccc˙ÚÚÚ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙øøø˙õõõ˙÷÷÷˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ööö˙ķķķ˙ööö˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ŲŲŲ˙___˙ ˙ī‰NËü'''˙ĒĒĒ˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙øøø˙ŗŗŗ˙+++˙üËM(ĸõ˙˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙÷÷÷˙ŠŠŠ˙ ˙ųą1$žõ˙›››˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úúú˙ŧŧŧ˙///˙ûŧ93ĩú)))˙ĩĩĩ˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ŌŌŌ˙FFF˙ūĶNHÍū???˙ĖĖĖ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ááá˙ccc˙ ˙æg aâ˙\\\˙ŪŪŪ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ėėė˙‚‚‚˙˙ķ…zī˙www˙ééé˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ķķķ˙œœœ˙!!!˙ųĄ"˜÷˙”””˙ņņņ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙øøø˙ĩĩĩ˙444˙üŊ3-´û---˙­­­˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙üüü˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙øøø˙đđđ˙øøø˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ÉÉÉ˙JJJ˙ūÕHAÎūCCC˙ÄÄÄ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ķķķ˙ĪĪĪ˙›››˙›››˙ŲŲŲ˙ųųų˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ųųų˙×××˙‰‰‰˙ddd˙˙ĖĖĖ˙ņņņ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ÜÜÜ˙ccc˙ ˙čb Zã˙[[[˙ÖÖÖ˙ũũũ˙˙˙˙˙ūūū˙ûûû˙ííí˙ÆÆÆ˙„„„˙???˙˙˙LLL˙ĸĸĸ˙ÛÛÛ˙õõõ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙õõõ˙ÚÚÚ˙   ˙KKK˙˙ ˙˙===˙~~~˙ÆÆÆ˙ííí˙ûûû˙˙˙˙˙ūūū˙ééé˙}}}˙˙õ€yō˙www˙æææ˙üüü˙ööö˙ßßß˙¯¯¯˙lll˙'''˙˙ũųúū˙DDD˙‰‰‰˙ĀĀĀ˙äää˙ôôô˙úúú˙ũũũ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ũũũ˙úúú˙ķķķ˙ããã˙ŋŋŋ˙‡‡‡˙CCC˙˙ūųō÷ũ˙)))˙ggg˙ŦŦŦ˙ááá˙õõõ˙đđđ˙˜˜˜˙"""˙ũŖ˜û˙ŒŒŒ˙āāā˙ĶĶĶ˙¤¤¤˙\\\˙'''˙ ˙üņÖŗŧäøū˙333˙eee˙”””˙ģģģ˙ØØØ˙įįį˙ņņņ˙ööö˙ųųų˙ûûû˙üüü˙üüü˙üüü˙üüü˙ûûû˙ųųų˙ööö˙đđđ˙æææ˙×××˙ēēē˙“““˙ddd˙222˙˙ũøãŗ‹§Õđü ˙$$$˙bbb˙ŸŸŸ˙ËËË˙ŸŸŸ˙000˙˙ŋ-'ļū ˙sss˙‹‹‹˙XXX˙'''˙˙úęș^09wąØđû ū˙555˙ZZZ˙www˙˙ĸĸĸ˙¯¯¯˙ššš˙ŋŋŋ˙ÃÃÃ˙ÂÂÂ˙ŋŋŋ˙ššš˙¯¯¯˙ĄĄĄ˙ŽŽŽ˙uuu˙XXX˙444˙˙ ūûđ×°t3*\”Čéú ˙$$$˙TTT˙___˙&&&˙˙ŲE=Ņ˙˙%%%˙˙ūûé‰L" 4d™Äåöüū ˙˙"""˙---˙666˙<<<˙???˙???˙<<<˙555˙,,,˙!!!˙˙ ˙ūüõä×b2N†žëûū˙ ˙˙ėcUå˙˙˙úåēuA $Bj’ļÕæōųũū˙˙˙˙ūũøņæÔĩiA$ >{ĩäų˙˙ö}Xåūųā­k7 0Jbz‘Ŗ°šŊŊš°ĸyaI/ 4jĒŪöãf+Ĩā¯a)  !'**'!  'X„l ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙€˙˙˙ū˙˙ø˙˙đ˙˙Ā˙˙€˙˙˙ūü?đđāĀĀ€€@€ø?āū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙(0` $  ;Z~ ÁßņúúņŪÁŸ}Y: =h” Ūņú###ū,,,˙111˙444˙444˙111˙,,,˙###ūúđ ŪÁ“g< E|°Øī...üHHH˙ddd˙|||˙˙ŸŸŸ˙¨¨¨˙ŦŦŦ˙ŦŦŦ˙¨¨¨˙žžž˙˙{{{˙ccc˙GGGū---ûīׯ{D 1rŗŨôCCCũooo˙žžž˙ŧŧŧ˙ĶĶĶ˙ááá˙ëëë˙ņņņ˙ôôô˙õõõ˙õõõ˙ôôô˙ņņņ˙ęęę˙ááá˙ŌŌŌ˙ŧŧŧ˙˙ooo˙BBBüôŨ˛q0 KœØôAAAũ………˙ģģģ˙ÜÜÜ˙đđđ˙øøø˙üüü˙ũũũ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ũũũ˙üüü˙øøø˙đđđ˙ÜÜÜ˙ēēē˙ƒƒƒ˙???üô؛JoĘ ķ222ũ†††˙ĪĪĪ˙đđđ˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ûûû˙đđđ˙ÎÎÎ˙„„„˙000ũ ķÉn$„āüZZZ˙ŗŗŗ˙īīī˙ūūū˙˙˙˙˙˙˙˙˙ūūū˙ûûû˙øøø˙÷÷÷˙øøø˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙üüü˙øøø˙÷÷÷˙÷÷÷˙úúú˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙īīī˙ąąą˙YYY˙ü߃#$~ā'''ũsss˙ÄÄÄ˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ėėė˙ĪĪĪ˙ŧŧŧ˙ĩĩĩ˙ģģģ˙ĖĖĖ˙ååå˙ööö˙ũũũ˙ûûû˙īīī˙×××˙ŋŋŋ˙ĩĩĩ˙ēēē˙ËËË˙äää˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ôôô˙ÃÃÃ˙qqq˙'''ũß|#oÔ"""ûyyy˙ÎÎÎ˙õõõ˙ūūū˙˙˙˙˙˙˙˙˙úúú˙ÛÛÛ˙ĄĄĄ˙uuu˙nnn˙ooo˙ooo˙sss˙ŽŽŽ˙ĀĀĀ˙ååå˙ØØØ˙ĻĻĻ˙{{{˙nnn˙ooo˙ooo˙rrr˙˙ÕÕÕ˙øøø˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙õõõ˙ÍÍÍ˙xxx˙!!!ûĶnZŋ÷ppp˙ŌŌŌ˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙āāā˙ŽŽŽ˙aaa˙‰‰‰˙ĢĢĢ˙ļļļ˙ŽŽŽ˙•••˙nnn˙ggg˙ƒƒƒ˙uuu˙bbb˙€€€˙§§§˙ļļļ˙¯¯¯˙˙eee˙………˙ŲŲŲ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙÷÷÷˙ŅŅŅ˙nnn˙öžY @Š īWWWūĶĶĶ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ņņņ˙ŖŖŖ˙^^^˙ššš˙įįį˙ööö˙ųųų˙÷÷÷˙īīī˙ČČČ˙mmm˙---˙<<<˙“““˙ßßß˙õõõ˙ųųų˙÷÷÷˙ęęę˙ąąą˙YYY˙œœœ˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úúú˙ŌŌŌ˙VVVū î¨? "„ā<<<ũˇˇˇ˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙×××˙qqq˙~~~˙ßßß˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ĘĘĘ˙iii˙AAA˙FFF˙‰‰‰˙ããã˙ūūū˙˙˙˙˙˙˙˙˙ūūū˙ééé˙zzz˙qqq˙ØØØ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ļļļ˙;;;ũāƒ" NĀ!!!ø„„„˙ééé˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ÂÂÂ˙kkk˙ĒĒĒ˙õõõ˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ááá˙ƒƒƒ˙ggg˙ĸĸĸ˙˙```˙ĻĻĻ˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙øøø˙ŖŖŖ˙lll˙ĮĮĮ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ééé˙ƒƒƒ˙!!!÷ŋM „ æNNNūžžž˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ŌŌŌ˙ĄĄĄ˙ÕÕÕ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙ōōō˙ŠŠŠ˙]]]˙ŖŖŖ˙įįį˙ÖÖÖ˙€€€˙iii˙ĪĪĪ˙üüü˙˙˙˙˙˙˙˙˙ũũũ˙ĪĪĪ˙ĄĄĄ˙×××˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ŊŊŊ˙MMMū æƒ4ˇų˙ááá˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ôôô˙ééé˙ööö˙ūūū˙˙˙˙˙˙˙˙˙üüü˙ÖÖÖ˙mmm˙ooo˙ŅŅŅ˙ōōō˙ëëë˙¯¯¯˙UUU˙™™™˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ééé˙õõõ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙āāā˙~~~˙ųļ4\Ū<<<ūĢĢĢ˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ööö˙¯¯¯˙BBB˙WWW˙ˆˆˆ˙‘‘‘˙ŽŽŽ˙vvv˙<<<˙kkk˙ŪŪŪ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ķķķ˙ĢĢĢ˙;;;ūŪ[õ___˙ÍÍÍ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ÉÉÉ˙ƒƒƒ˙nnn˙mmm˙lll˙nnn˙rrr˙yyy˙ĒĒĒ˙ëëë˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ÍÍÍ˙^^^˙õŒļũyyy˙ßßß˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ööö˙ččč˙āāā˙ŨŨŨ˙ÜÜÜ˙ŪŪŪ˙ááá˙įįį˙ķķķ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ßßß˙xxx˙üĩŲ"""˙ŒŒŒ˙ęęę˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ūūū˙ūūū˙ūūū˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ęęę˙ŒŒŒ˙"""˙Ųņ(((˙˜˜˜˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ųųų˙īīī˙ėėė˙ôôô˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙÷÷÷˙īīī˙đđđ˙øøø˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙˜˜˜˙(((˙đú+++˙˙ņņņ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙æææ˙ŗŗŗ˙‡‡‡˙}}}˙˙ĶĶĶ˙ööö˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ŨŨŨ˙ŠŠŠ˙„„„˙‡‡‡˙¯¯¯˙âââ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙đđđ˙˙***˙úķ(((˙™™™˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ÛÛÛ˙}}}˙(((˙ ˙ ˙˙VVV˙ļļļ˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙øøø˙ÆÆÆ˙eee˙˙ ˙˙)))˙rrr˙ŌŌŌ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙™™™˙(((˙ōŨ###˙ŽŽŽ˙ëëë˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ĻĻĻ˙///˙˙ ˙:::˙LLL˙<<<˙ttt˙ŪŪŪ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ččč˙ƒƒƒ˙˙˙˙BBB˙EEE˙>>>˙–––˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ęęę˙ŽŽŽ˙###˙Üŧũ|||˙ááá˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ííí˙˙˙˙)))˙ĸĸĸ˙ÂÂÂ˙}}}˙```˙ÉÉÉ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙×××˙YYY˙ ˙˙DDD˙´´´˙ļļļ˙ccc˙uuu˙ååå˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ááá˙{{{˙ũģ“÷bbb˙ĐĐĐ˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ííí˙˙˙˙111˙ģģģ˙ÜÜÜ˙‘‘‘˙eee˙ÉÉÉ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ÖÖÖ˙WWW˙ ˙ ˙SSS˙ĶĶĶ˙ÔÔÔ˙ttt˙vvv˙äää˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ĪĪĪ˙aaa˙ö‘g æDDD˙ĩĩĩ˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙¤¤¤˙,,,˙˙˙```˙{{{˙VVV˙vvv˙ŨŨŨ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙æææ˙|||˙˙˙%%%˙uuu˙xxx˙PPP˙“““˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙´´´˙CCC˙ åe:ŋ###ûˆˆˆ˙ååå˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙āāā˙‡‡‡˙444˙˙˙...˙ccc˙žžž˙ööö˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ųųų˙ÉÉÉ˙iii˙&&&˙˙˙555˙www˙ÔÔÔ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙äää˙†††˙"""úŊ8‹ęTTTūÄÄÄ˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙âââ˙­­­˙ƒƒƒ˙zzz˙———˙ÍÍÍ˙ôôô˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ööö˙ĶĶĶ˙ššš˙xxx˙zzz˙   ˙ŲŲŲ˙ųųų˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úúú˙ÂÂÂ˙RRRūéˆ VĮ'''ų˙îîî˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ôôô˙ééé˙æææ˙īīī˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙đđđ˙ååå˙æææ˙ņņņ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙˙&&&ųÆT -š í___ūŨŨŨ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙äää˙lll˙đ / ‰ č[[[ūÜÜÜ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ččč˙sss˙đœ**›īttt˙ččč˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ņņņ˙ŽŽŽ˙÷˛:<´!!!÷˙ōōō˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙÷÷÷˙¨¨¨˙111ûÉO OÉ111û¨¨¨˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙úúú˙ŊŊŊ˙EEEūÚffÚEEEũŊŊŊ˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ûûû˙úúú˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ÎÎÎ˙[[[ūé€é[[[ūĪĪĪ˙üüü˙˙˙˙˙˙˙˙˙üüü˙đđđ˙ĪĪĪ˙ÃÃÃ˙ååå˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙âââ˙°°°˙§§§˙ÔÔÔ˙ķķķ˙ũũũ˙˙˙˙˙ūūū˙ŨŨŨ˙sss˙ôš "õuuu˙ŪŪŪ˙ûûû˙õõõ˙ááá˙ļļļ˙vvv˙:::ū---ū```˙ĢĢĢ˙ŨŨŨ˙ōōō˙ûûû˙ũũũ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ũũũ˙ûûû˙ōōō˙ÜÜÜ˙ĒĒĒ˙^^^ū$$$ũüDDDūƒƒƒ˙ÂÂÂ˙ččč˙øøø˙ččč˙˙$$$û¸14ŧ$$$ü‰‰‰˙ÖÖÖ˙ÍÍÍ˙™™™˙ZZZū$$$ųīÜŲëøPPPũ‡‡‡˙ˇˇˇ˙ĶĶĶ˙äää˙îîî˙ôôô˙÷÷÷˙ųųų˙ųųų˙ųųų˙ųųų˙÷÷÷˙ôôô˙îîî˙äää˙ŌŌŌ˙ˇˇˇ˙………˙OOOũøęĐĮß ō,,,ûiiiūĒĒĒ˙ÉÉÉ˙–––˙111˙ÔJJÔ$$$ūlll˙„„„˙XXXū&&&ø įɟog” áķ;;;ü]]]ū}}}˙–––˙¨¨¨˙ļļļ˙ŊŊŊ˙ĀĀĀ˙ĀĀĀ˙ŊŊŊ˙ĩĩĩ˙¨¨¨˙•••˙|||˙\\\ū;;;üōáÁ’\Mw§Đí222û```˙___˙'''˙įgfį˙&&&ū###účÁŽ[1)R€ŠÎãņ'''ø333ü???ūGGG˙JJJ˙JJJ˙FFF˙???ū333ü&&&øđâͨQ( 8ešÎîû ˙ô‡xđúč†L& 5Wv”ŦÁĐ Ų Ü Ü ŲĐĀŦ“uV5 -[•ÉėņŽXÉÃ~A  *7DMRRMC7*  #K€’J˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙Ā˙˙€˙ūü?ü?đĀĀ€˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙( @   &Q| ­ÎîúúíÍ Ŧ{Q&&kĨŲ///đPPPûfffū{{{˙ƒƒƒ˙ƒƒƒ˙{{{˙fffūPPPû///đؤk&jˇ)))í]]]û   ˙ČČČ˙äää˙īīī˙ööö˙øøø˙øøø˙ööö˙īīī˙äää˙ČČČ˙   ˙\\\û(((íļi(…ßLLLø¯¯¯ūŨŨŨ˙õõõ˙üüü˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙üüü˙õõõ˙ŨŨŨ˙ŽŽŽūKKKøŪ„'1 ē...ö———˙æææ˙ūūū˙ūūū˙ôôô˙įįį˙âââ˙ččč˙øøø˙ūūū˙üüü˙ķķķ˙äää˙âââ˙îîî˙úúú˙˙˙˙˙ūūū˙ååå˙•••˙---ö ē0+—111ö”””˙ėėė˙ũũũ˙ūūū˙ííí˙­­­˙ŒŒŒ˙‡‡‡˙˙¸¸¸˙äää˙×××˙ĻĻĻ˙ˆˆˆ˙‡‡‡˙™™™˙ĪĪĪ˙üüü˙˙˙˙˙ũũũ˙ėėė˙“““˙000ö–*’ëĢĢĢ˙ņņņ˙˙˙˙˙˙˙˙˙ęęę˙˜˜˜˙………˙ģģģ˙ĘĘĘ˙ēēē˙€€€˙ggg˙hhh˙‘‘‘˙ÅÅÅ˙ÉÉÉ˙žžž˙yyy˙ÖÖÖ˙ũũũ˙˙˙˙˙˙˙˙˙ņņņ˙ĒĒĒ˙ë‘ QÜtttũōōō˙ūūū˙˙˙˙˙ūūū˙ĀĀĀ˙xxx˙×××˙úúú˙ũũũ˙ųųų˙ĒĒĒ˙JJJ˙[[[˙ÅÅÅ˙üüü˙ũũũ˙ėėė˙‘‘‘˙ŸŸŸ˙ôôô˙˙˙˙˙˙˙˙˙ūūū˙ōōō˙sssüÛQ *¨SSSûŅŅŅ˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙¯¯¯˙ĨĨĨ˙úúú˙˙˙˙˙ũũũ˙ŨŨŨ˙xxx˙¤¤¤˙’’’˙………˙īīī˙˙˙˙˙ũũũ˙ÉÉÉ˙˜˜˜˙ėėė˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ŅŅŅ˙RRRû¨*TŲ‘‘‘˙īīī˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ááá˙âââ˙ūūū˙˙˙˙˙ķķķ˙   ˙‡‡‡˙ßßß˙ÍÍÍ˙rrr˙ÃÃÃ˙ûûû˙˙˙˙˙īīī˙ÚÚÚ˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙īīī˙˙ØT•555öĖĖĖ˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙âââ˙xxx˙iii˙ƒƒƒ˙~~~˙\\\˙ĄĄĄ˙ôôô˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ËËË˙444ö” ÄSSSũååå˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ŅŅŅ˙ššš˙ˇˇˇ˙ēēē˙ĀĀĀ˙äää˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ååå˙RRRũ Ãëlll˙ōōō˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ųųų˙ûûû˙ūūū˙˙˙˙˙˙˙˙˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙úúú˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ōōō˙lll˙ëúuuu˙õõõ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙øøø˙ÖÖÖ˙§§§˙˛˛˛˙ééé˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙îîî˙ēēē˙ŠŠŠ˙ĶĶĶ˙÷÷÷˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙uuu˙ųōqqq˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙§§§˙444˙˙'''˙iii˙ĶĶĶ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ŨŨŨ˙nnn˙˙˙BBB˙ŸŸŸ˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ķķķ˙ppp˙ō Ųaaa˙ííí˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙ggg˙ ˙???˙’’’˙ggg˙­­­˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙ĩĩĩ˙...˙˙ppp˙}}}˙rrr˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ííí˙```˙ ØŠAAAúØØØ˙ũũũ˙˙˙˙˙˙˙˙˙˙˙˙˙õõõ˙fff˙ ˙OOO˙ąąą˙www˙ŽŽŽ˙ūūū˙˙˙˙˙˙˙˙˙ūūū˙ŗŗŗ˙+++˙˙ŽŽŽ˙œœœ˙xxx˙đđđ˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙×××˙@@@ú§u###ėŗŗŗ˙øøø˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙ĢĢĢ˙;;;˙˙@@@˙vvv˙ÖÖÖ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ÜÜÜ˙ooo˙˙000˙VVV˙ĸĸĸ˙úúú˙˙˙˙˙˙˙˙˙˙˙˙˙÷÷÷˙ąąą˙"""ës8 Ŋjjjũßßß˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙÷÷÷˙ŅŅŅ˙ĸĸĸ˙­­­˙ååå˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ũũũ˙ččč˙ŽŽŽ˙˙ÉÉÉ˙ķķķ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ßßß˙hhhũ ģ6„555öŧŧŧ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙üüü˙÷÷÷˙øøø˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙øøø˙ööö˙ûûû˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ÃÃÃ˙999÷‡ k+++ņˇˇˇ˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ÆÆÆ˙999ö}„AAAøĖĖĖ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ØØØ˙RRRü™$§```ũāāā˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ūūū˙˙˙˙˙˙˙˙˙ččč˙tttūē04 Ā{{{ūëëë˙˙˙˙˙ūūū˙ííí˙ŲŲŲ˙ōōō˙ūūū˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ūūū˙ņņņ˙ĮĮĮ˙×××˙ųųų˙˙˙˙˙ņņņ˙˙ĐDSŨœœœ˙æææ˙ËËË˙•••ūEEE÷"""ōVVVúžžžūÖÖÖ˙ëëë˙÷÷÷˙ûûû˙üüü˙ũũũ˙ũũũ˙üüü˙úúú˙÷÷÷˙ëëë˙ÕÕÕ˙ūUUUųí'''ņnnnû¯¯¯ūŪŪŪ˙ĒĒĒ˙égq턄„˙ŠŠŠ˙AAA÷ā­ŊãLLLųzzzūĻĻĻ˙ŊŊŊ˙ÍÍÍ˙ŌŌŌ˙ŌŌŌ˙ÍÍÍ˙ŧŧŧ˙ĨĨĨ˙yyyūLLLųãŧ~ Ę&&&ėhhhũwww˙õ‰–÷ōŌŠP 'SŽ ˇÖ$$$å111đ666ķ666ķ111đ###åÖ ļŽS& 3gĩâ÷Ŧ‰ÖM  /K`vu_J. 2fŠ|˙˙˙˙˙˙˙˙ü?đđĀ€˙˙˙˙˙˙˙˙(    """j<<<ŖVVVā^^^÷^^^÷VVVā<<<Ŗ"""j<###‹‹‹éŧŧŧųŨŨŨ˙æææ˙æææ˙ŨŨŨ˙ŧŧŧųŠŠŠé###; HfffëÎÎÎūėėė˙ĘĘĘ˙ÃÃÃ˙ĶĶĶ˙ÎÎÎ˙ÁÁÁ˙ĶĶĶ˙õõõ˙ÍÍÍūeeeë G/444˛ÕÕÕūúúú˙ŧŧŧ˙ŋŋŋ˙ĖĖĖ˙˙˜˜˜˙ĐĐĐ˙´´´˙ÕÕÕ˙úúú˙ÕÕÕū444˛....‘ŖŖŖõ˙˙˙˙ūūū˙ĶĶĶ˙õõõ˙ĖĖĖ˙ĄĄĄ˙œœœ˙ØØØ˙ééé˙ŪŪŪ˙˙˙˙˙˙˙˙˙ŖŖŖõ...GGGÍÎÎÎũ˙˙˙˙˙˙˙˙ûûû˙ūūū˙ÉÉÉ˙ĨĨĨ˙§§§˙ØØØ˙ũũũ˙üüü˙˙˙˙˙˙˙˙˙ÎÎÎũGGGÍWWWøããã˙˙˙˙˙˙˙˙˙ÛÛÛ˙ŋŋŋ˙ėėė˙ũũũ˙ūūū˙îîî˙ĀĀĀ˙ÚÚÚ˙˙˙˙˙˙˙˙˙ããã˙WWWøVVVķááá˙˙˙˙˙üüü˙iii˙YYY˙ĢĢĢ˙õõõ˙÷÷÷˙ĄĄĄ˙KKK˙ƒƒƒ˙úúú˙˙˙˙˙ááá˙UUUķ@@@ģÃÃÃü˙˙˙˙üüü˙kkk˙iii˙˛˛˛˙ööö˙÷÷÷˙ŸŸŸ˙WWW˙˙úúú˙˙˙˙˙ÂÂÂü@@@ē%%%{’’’ī˙˙˙˙˙˙˙˙ÚÚÚ˙ŋŋŋ˙ėėė˙ūūū˙ūūū˙ííí˙ŊŊŊ˙ŲŲŲ˙˙˙˙˙˙˙˙˙“““ī%%%{\‚‚‚æ˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ë g(((yņũũũ˙ōōō˙üüü˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙˙ûûû˙ęęę˙ûûû˙§§§ô---†)))̃ƒƒönnnÛ...ˇYYYЕ••ëÁÁÁúĖĖĖüĖĖĖüÁÁÁú•••ëXXXĐ ĢXXXĪ}}}ô)))¸ ą$$$ŗO&?&&&m???ĄHHHļGGGļ???Ą&&&l??› ¨˙˙Ā€˙˙merecat-2.31+git20220513+ds/www/icons/folder.gif000066400000000000000000000003411424066547300207710ustar00rootroot00000000000000GIF89aÂ˙˙˙˙Ė™Ė˙˙™f3333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,T(ēÜū0ĘIYšøÎēīEÄ}dĻ)Å@zj‰C,Į+ŽõWž<œė IEáqg<˛‚N"4ÚIRaV˜VÅŨÂx‚Ôl<&€Ãæ´zÍ#,LđŒ;;merecat-2.31+git20220513+ds/www/icons/folder.open.gif000066400000000000000000000003621424066547300217340ustar00rootroot00000000000000GIF89aÂ˙˙˙˙Ė™Ė˙˙ģģģ™f3333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,e(ēÜū0ĘIĢm&ë|—`Ũ'ž™g’ëē°#ŖsĘŨo Ą uÂaĄˆJ:Ÿ„Ĩā|ZQ˜Ŧzuf=ŊŽW pÅÁ/ãsFĢ×mņC¨Ûī÷9Ļīûũ*:ƒ5 ;merecat-2.31+git20220513+ds/www/icons/forward.gif000066400000000000000000000003331424066547300211630ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™fff333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,NēÜū„„ņ\ŧDԅīÜA‡i•čQy‘NšßS¸/ÎEĻH`€ãēmņ+}Cdqųã0Ÿ3… U ¨O)ÛlļÔŪwŲk%ˊ3ú‘;merecat-2.31+git20220513+ds/www/icons/generic.gif000066400000000000000000000003351424066547300211350ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,P8ēŧņ0 @Ģ#ži;Ī×U"¸×‰(1¸đ‰RDmōL ŧ€K:KīĮ |9㑨Ŧ ÍeŌø,ĢQ)´‰všL/qĐ+›y¯ČmÍū%;merecat-2.31+git20220513+ds/www/icons/image.gif000066400000000000000000000004651424066547300206070ustar00rootroot00000000000000GIF89aã˙˙˙˙33Ė˙˙ĖĖĖ™™™ffff333™Ė™33f!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,đČI§¸XĀģßGvm^I†Yv*:A, ÚØ6˰ŋākë-ŽČŐØãžĨ6KX5TĒ“%8kVË bßđ•4ˆžöjŨ îi‡ž đe,|y7W„†‡ŠŽ/+f”•z%~™€–w Ŗ¤Ÿ›—%ˆPŦ­O@I˛ŗR;merecat-2.31+git20220513+ds/www/icons/index.gif000066400000000000000000000004141424066547300206260ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™fĖ˙333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,Hēŧņ0@Ģ$ži;Ī×U"¸×‰hA¸đ‰REmr%ė‚ÎãŨËFč~2K0^Æ#đChZ €VyR2ĩVåkX›(›€ëĀÕpIÕÖīg–Õ7ĸVĪn§EMOrZR9„DQ‡A†G<Ž9‘”Œ s˜˜/yy ;merecat-2.31+git20220513+ds/www/icons/movie.gif000066400000000000000000000003631424066547300206410ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙ĖĖĖ™™™fff333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,fhēÜĐáÚ4%^Â;• 1œč.ch˜é ^íËnģĀņĒÔ^j&ŌoĒ\dh: `…A3ž<"fŠĸ|[’íĻ•zqUŌ 6ŗ•â#ÎxÚŖÖŠ"íŌÁ‚ƒ„ ;merecat-2.31+git20220513+ds/www/icons/text.gif000066400000000000000000000003451424066547300205060ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,X8ēŧņ0 @Ģ#ži;Ī×U"¸×qDëRÄĪ' ģnˇ–ā „š­ō Jnģ(Ŧ¤Ā<:{P¤ôŲŧVEÚé÷Šín—Y2ēëü~đ¸O‘ڍ ;merecat-2.31+git20220513+ds/www/icons/transfer.gif000066400000000000000000000003621424066547300213450ustar00rootroot00000000000000GIF89aÂ˙˙˙˙33Ė˙˙™™™f333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,e(*ÕŊ0Ž-2ŗJ­Ž]į}JXqä뒝!Ī|C>Ô_@Ü9B įģU„CI`ų3"“‹ĨtJ]FĢØŠâ™ĨO‚x,–~!Čæ-ĒOîØÛčģģ1˙ičt-)44 ;merecat-2.31+git20220513+ds/www/icons/unknown.gif000066400000000000000000000003651424066547300212230ustar00rootroot00000000000000GIF89aÂ˙˙˙Ė˙˙™™™333!ūNThis art is in the public domain. Kevin Hughes, kevinh@eit.com, September 1995!ų,h8ēŧņ0 @Ģ#ži;Īaš"¸QåyŠ€Ęĩ.J 7ąŽ0Ö§ĢŌ#é$ @*í,KëûÕ|ŋ'ˆÍ"-ŊŽķK<%ĮÕ$”ÚŠŲ`ķ[˓¯éj%VÄI>ŗ“ĄFä ęB}įL4j1Ė-Ņâ˙O•Į.¯Đ/?ÎŪ^[z‹x1>^Pey}u’å „&ŒG>˜70ƒ[K$é•,~˛7aŲVŌž3 ÆK•ÚŊŽ  …PęãĖ ”:[ƒđpŦ+ī6ŽČDëęûæ?P@ėq.WLÖDã $AOĒVáI”é.ÄÔĸr !GÖYɡ>—Ųŗ#J=R´;čaúwĘ}[Ę{yŊ/%­Ęjŗ&ÛoHōO\ōܞâ~ˆ īDđ?|üķ7d¯'Ímgˇáũˇ›Įđš$ą§č<õoĒņ?°ųŠ\ĮÎŧŦĻĨfwĶ ôGą"RŽJ)E>ĻāoÆqēĩČhb_ÂÜtíįÃpbŠÃčGæ$KQ|‚#ęļ8]ÚūP07jäēŠmŅŊātKŅüxWüÖ¤ĢD“úX~VGD×ØĒTæzcČq4°ŗ.Ŗ¸īōyëĸĀō]ĘĸIäRH"UmlĸTõՔθ48Ëĸ7/ĒōˇųĶqģųÃhėĩ9&ųæĢļb‘7ĻŽÃėļąô|ķUdĘ\#aõ–ĨģWm•Ũ š€9ęäŠQOE¯)íå̌…8ˆ„A ū¯éš–Î!¨ét ŗ‘ãbš(–_ÛĄ…5öāgôyî„ĩŌki&šU}õŨI•LnVĻę'‡TõvüĀh°ˆŽf‹Ąo$*Ų_Ú%üôrd˜˜Åĩ0í”Õ…pĖŊú‰ŊږHs =s!×$w_ˇO HŖęxßîĖBžMq¨ XjšAĢ/'ÄG.)ąmķûžšląĩ7ŸÚŠ÷G9¯cËR.é?+*Ŗ–°ƒb~ŗčĩĐ" ĶĒ΃U•Û…ŖŪ#*˛Ú/ÃâėŧßĻhŠÕĩãÉK ŗ-\ 1Đõs­*üzīEžÎyũ1[Ģ,ޝk–ôzįž="ŗËđķ˯LũI$ė-<ƒ3së^ųΝķPĩl1Žy’M¨b¨é?HZd€kXãēĨĸį§š×G˛äę\ĨŌnˆ4%cčI˛Ĩ>ƒƒĶÕJōīFÂÖk؎UåžēÛ FߋFŲü†¯ģđO´ŠL[F+ŪÃė%õŨbga]Ĩ•Ú%ävfĩÅzPXq^ā tĖˆÚÕE Ш.ž.˜š8ėöļ„ĻĶŽYĮŅ3]h‘Ư7ˇ„-æZÜÖâŅÃgõųęO@:oGFÂŗ×¤d:E,éļTÎÄ–5ėžCōFI˛Ū–Ā*°û’ŧ\iđ;x\%‡§ĖҊĻķßC 49Ę,š ļ­eˇÕUfĘŲËj%l—NÚjĖ@Stž§âŽG”VĩŲhámë›]*nāΊCîŲ_R)ĪŊθqmS⥭ë)€ú°úg™dœĀ§rtņŪ߁rø˙#öLŽÔE›/D Čs/ŗŦžõTōô"–~žˆ\E×s˜ąʖ9­ˆ->Líĸ6¸<+oeh›BéN§čPļ‘ŋ5ĩ­[ĪÂhŗĸ5ŨÎēüá98QĨë2Z¸RĻĢb] uhÜ/ÃÖ{#7âéį~„^ą‰Š–žÛņ0ûŋ oŅâŋ&Co%°ÆŒX[íÛ5JÅgXOšxį§q˜ŅrUáā/°”Ô2uN<éZ#ÎJ°ĐŠcQek]U WfLįü^`Č/2đ\+î6lžŪÚ%ÚæŽtXũ(j—U˛dŗĖģŠĄŌjï1ī3ĨîĶ7“ą9q<ôšcŸGuđ>ąå÷˜˙NģMĀĩü”ĐhĐŧäĄ*Z'ŀdV͞ĸúúšíŒ$ĘÕō'ŧ螺ė…)žš_%ö0ú_*š15 @䕮’„m}ëŖP`LĒŗ\XŽsˆõ™›Å–•Ž7tĩĒÛaˆģdųJ|UŅfųŨ>ØŨ†CdQ6y˛ÖšJŨwÖ\ŦňyXn8CXŽ$3ŠŖ1Zî_Xlę]jŗ¨Á ŦaÎ-Ú¨05­”ž•‹c8>• X1ĢĒē:Ŗ]RĒŌ.UØ´Øļ  éLŲ"Ļ {—õs8  ņŅ0ū{Fņ/‹OĐÚÎũlšņÂS*BŨĘēZęéRā›2p ŠK‹Č—úĻķ¤āĐŧÛZēū‚ ÷evĻĄœ<Čr¤žj8Īę˛Õ:2â3(H´JXĨč EZ9čĮžÎ^ĢSčRŪŖEí Ú.A›—ČÉhg]œtEwDčįę­i–å#¨ô|Ā3ä÷ú*-Ę'…¤l¨0•ĘœõũdXXĻΛĐ%÷%ŠJMtĒēIôs¨ēŗ2NÃ“Ē˛ŋËāÖ˛Y`öĄˇč|¤H 'V§šūč(ÔC=L1x…+ž: ĩÂlÕésšŲŠ~]2ū*ŨVB€å<éÃWŠByO¸œ…uVdq ąy ‡¤Ëtą˛í~ģ—°u¤ö’Å;§SiMáƟ&ĻSĸÛ âĐšĪõō])uŅ‹4­šú/nĄė_Đ´bØyŧÃÎũĮŧÔĸ2‡o7Ũ„xŲk™×Iņã˛É%zŖRÔŲ6‡näöl@ÄķŪ¨#™ĮŗęHēaĖ~ģå|ŨÂ×X°${EadJÄ îã.ĶĖ÷8jŽÄĄXJ€et ¨Ĩi˛æāõž}e;Zšā¨d•ã—šũÖ'bvk„Û=„Ņ ŧŠsi¨S“ņčĖaIüũ+Ä, '\))’ėwmŦ‹m#ĨY¨eŽ9jfĢúøû}eĀKįč#uįŪã0Ú ˛OãūŠäk͟¯ęģšyÚīpVˇö׊@ü•ø)ĸ#ÁēgÚˆĻ˛cRÚöÔ˛Tįš÷ōwōTQ˜€uWY64üÄr¸ qSq3ø#É\Âąsēĸ/“•æt9ÚLÛˇ lŪÁđoōÚéļy%¤ĘËš<ĶMŅDąÁ]ŧƒF‘]\×Čõ˛6ŧũĒ|Š€Į€+ĻOW˜×ߝ‹Œųz@ÆiķĒÉŖšLÂÆÅˇōRđŽ\ŲpgQБ…ÃZKˍänŌ;SˆÔ´ĪŌ[1­ƒŠ€5E$ΞQĶë”oË ]nØÎš''¯Mæî-%Š­ŽŖę§IK§vĻĒæJ9Áôd VJ˛ Œ,vŒĸSĸ˛aŠÆëgFTYŪE iã>Yķ*·1ŧ[ĐIuRÕrRCd \6t‚2ĢĐĘŠ7ŨLJž‡<ÔQdPf[Ŗ\ĢEb—=Xė(É\ĸ˛ŨĨGĢŲb'Ų)USTĮR[2†gÍĐĻës+Ŗ…ÚOxė‘^fzU^™ŌŽžuuŪ5šJęgRlœęÁĘm<œĒÍUúpčHSe9žÎ9Q6̜JéĮ&ō ];0Ēß§Ÿ—BL‹“ŊEhÛĻĖŊ ŖíÕy÷Qų°ĄNü_ŲYH$õ€YtĐmĸĐ/¤Ž‡Ņ’ą"^ÕJęōRX–‚9=ãˆß\@p,hüÆöë ÅGSuŗG˜t2Ėž&¯n˲1KŽ{Ö´ĒQ‘ĩŠ•ÔZ*Í~mönnŠ“Dé…kŨŠyi†>čžlõė[ilÎfîÔŧԌž6SÕËËéÖ§Ķg‚Öũt1%‘ų×[܍›jz‰ĘR¤š\ã/t‰_†Đ,1gúpϐžF/ĸwéü3z „Œģ0ԓp…ĶÕ}ž Äæ&D.ŧn]ō§,{IÁ!ãė9ÔØ‚sī™MŊEŖö“!fîŸ:xäö9” íÕé™xų‰:ĘîRv¸s€GPô­% YĢVIÉ´×ŌÂc?ĮkUrÅũ7:sž‘Ųų×ĸáčČ*ļ}\õęŧWN1ĪvĮQ¸Í:ŠĢ[SK1nŠ–ÉŖ¤!N,ÖČi=R 핮eŖĶBūW[BˆôŒ>Ģę čÅÃbi…`_:IVįya~‰p¸ÁEĀ@SĒÉQ•ŪīOyJ/ÂąJāj§%W0Vō'€čķ Š×K0š4´™ˇ`Jčmú}+`…¤(¸9Čm2;-53ú ĒPčsĮE˛–fI#¤É8[ŽÍh^k~˜gn…‡Ü”ģ9ĄĪ9@qTŪ7ŽÛeķũY§…F235\¤ísœų CŸ´ą^ōZā=!*y}C<βW6 ČIÖ %ŽUņ?ǝæĨ•‘Z E;}pD—žŠLOŦ̧tJRúËM:ÂT°Ė üŦ€Ü‡L;ŽķŒÕŌ°š.K\ķ´ h6×Öą…a-\HՏĩ6ĄÔĢ‹ØgCõėë aj2bs/ú"/N5MSfÍŗĨ˜J¤m& Ą‘T4&#†JäˆĻ JÅI㑃E”q] Q°-NVąWø˜P Š\+Š"b÷āøīŸ2Ēqį1„ÉĄHhˇX÷8#ŽiŌ°;Œ‚›NĘŽ$6`˜—P0 ¯Ę:įÕU÷ÍtWķ;gJlpû<ΰzTnY@(ĒįJ:ŧ<,1n·*čËÁ¤Đ™XÚŗ›f¯ŋeĘgi(`C•dô7ęŅÄlŠŦkPû tŧėĮ Tģ¯TšĨ‰ĻĚ c˛Ą´Blž-ū!m‡Ø đ_˛›Ëր[L…7 žcņ“ßŧúũ)uGÕY,D&šÉΞĶŲ=>OĢÜŗ;y§=ķķĸ6 ŒŧyzXÚ2 &°…jBëâļiCf§¤=EQ•U]÷´ŒYģ,>ŗŌN;É5%…Īi`ږã–{ŋæV_Cž<ž{kW­Ą)v‡ed{\Ũ8ŋhŦ‡qč:/Žčōzz‘VfķšƒlWI•Į/ōY‹âh!ÕÔ¨mœÛžæ:˜§msÖl˙<3FTÚ/UüúFˇŖŒ•†vŌt^ÆųU|Äŗ~íˆ:Ģyu–×Zé‡e8Ķx˛Ŗ÷ ÷>î|ĻwEĒûz†XĶ•īrÛŨŗ€Ŋ+dR´›lŋ9ûĮ‰Æē}ŋœzÅdw”0Įī~G랡¸ā7ø Œ8ļĖb6øISŪ—ēķēĪĶŧßŧ‹oRä{aĐm°ą¯ž¯a†´Ŋ+Į=—ļ”ŧË}ŧĶŲü‹×vđ„Œ×ƟĸP1Miå}#ķīč-°>›æ^›ˇ›čņ>ąŪBß;ˇ´(?#˛­öSļpĨmå۔`íčŪ]—3oqüĶú[ķbģ´zÔđŧ!đ*UKÃĸęģã>Âr|Áĩ­äZ‚¸ ¨~ûųëô/\r™Ŋ&r‹íxŨ–7noŧ͞ŲfŸļß?4ūĄÛ!æ;%ŧÖÎíņžÃŅūOčŪwč? ÷LVÛ,˙ą‡v،ķ{˙éH´ô>Cëߜv÷Tz:öķī?n iīxmÎĶôĖûÃ6ôäÃo7õīĖŋĸöđ öū}zá—8´ķĪ-ÚmŸôÜūƒoŅåŊ_fūk¸ķ-ŊO#ĩUļoĐ|[aļGY§Ãė`y?dŲ~#qâ›~Œü×úKÁ´`÷œYŲ”dĸĩ'>ĩ;Ŗ•üÃ텮ÎsęŒ8Zâ0JYtš}'!ŋA͔[ 6oˆŅą0ėƒįG‡Íé–HĨÚ|'5ãšÛÔĮũœÔ`Åô*amŲÖÛŨ= g4딪fĮŲãĪEJœÄuĶp¤¨=›yÜöŖUqLņvSēáAŖ œĻž5Œ¨]\‰šîĻꔋ´Õ¨@[ē˜h<ˇmså=}W;äÛģãœę=}e3¯‡Ķ3āÍĐ\‚áī™åHAãĐLĘhRÃ}Dq_;ĘõŠČ\´­fîũĮĒË ÎHVŨRš˜Î]—ÔOŌŌøIlv7XÃĻ8ρåÔ]9gQŖ›ķ °Ķž¤RĶžhISËŋ•O +JBîgXI´ƒeØÛ5ø´W&@‡]ÄudN`Ļb.V\Ĩĸ#6š!Y’}û¨MŠŦ´ģîáR~nsQÉĐMŽ5ŗ1pEˆ×æ4ÜĀ‹o m ”ÍĄ)ÂČz‹ÄU‘ĩÚ5ũĀø¨˛ēérQg%=1Į°Ę(JœæŦ<,€Nž3ÂW%ļ˜’#ŦƟļü“ÖüÅŅūīÆŊSĸ{?3ôī?ÛĐņû,Û^ã¤Ûɐč˛Q§é<æUiã~įáPŽ×{‚Ô^Zgâvõo-õ,NΚíŧĪÚŧ“Öö 'æūŊ°ē…ąÛĀØÎ/īYM_åK'Ēzæ?YÛAŌnŲhWļ#@#?ˇĸÕD˛Ē€; XVŖ×ŪÍthŧ3•G¤Å6Üč?J‚úå,ÕČ{Tsã>c`öYÎDéŊP$å&•ËąäģÁ¸=֖ø^H|•SÔˊHM›iÛĪĢō_GķvMļ‹ĪŊ.éĸÄhq‡zŸ–nŧÛm.“˙o?Ęh‘Å˙J`ĶdqųįÛü&OézŒNŽŠĻÄč2{.Aâ›z•ĮlŽ~n÷ŋ<ûΆhTč7á§aå-sßE˙OųV›"o@üáėģ*ͧîÂĢQč{xUûėœ_–üËĪĩ4ĀÜÔĶpĻ›ÅĄ) l$ß.žŧ>āâ<īŒ#bÔA Í* fŸ ôą6Âä1 ‘€‡a&pŧ‹F+RHX֑Wė%ŨĢZpy •Œ ´—^†gyĸîȝŅs6>Į†1Gņt`ĢŽ‹Į.}å:ꑱŨŌR˘¸™¸†Ũ\— K?>t–ĻV¯Æ#é/ÅJ¸&­Ŧ,+Ųķg2ējAR•`âŦīļ5āu Xšē¸JĸQ?‚­¤ĩ=0ŧđJw‰Ē˜æ˛ī¸~Ģā?˙Ä4!"1#25364A$ B%&7CD˙ÚĒZsDî€5íōcĒåEŠ J>[Ž^9V¨xŽT^ˇēމÕrĻĨĒZ÷Ž4ĩō4ELuJßø-‘ų/É—ÆžBĩKâšĩō4{|ĩZųHhÖŧü‚ųaé>äÔ>ĸF•|ĩ)õ/˛ü–™ĢšE—FæØPŧļŽôoEĢ{ŖLi>[Õ3×:šHã¸YŠ[Ëy(ē­5ÄŗÛŌO=’Õj—ä=™ņNwQÖ鎨æOČRŅ?=ĐĸđzŊč-Ũ?´†ˇāü¸Ō x“ŨŽęČTƒŌ‰Ē•Ēå….^¨Čũ[Īø^.ŋáXēŸĸmĩw îõV˜W(”FŠ›ËVęâ^Ęũ|Ĩ°`^t~*T‹ "õ'ü+CĸņuÔ5cÄcÜĩ†čĩEō({g5ʉĄRO’ĶÔboäh|…[Ŗ[Ĩ4hŸ¨SVž[Ŗįå!Ļ]ž4Õ =ēãå֞:íUēqŖ\|üÅ >\éžĢĘ×UeĻÄ[ÄũČž B˛a1ņdō…z*Wô}ô"šēĘÁë/i63#˙Ŧļ4Ლ}Ôj>ŊŊšŗ˛ĩs%ˇ]´–ĩ&sŠ#‡Ĩ..îđŨlÁzsŋô€ō•ē_’Ņ4æ…j›ĮūÕīKâˇGåēZ&šy&ˇ[ĄCå!ĨĄíFˇķ4WuĒÖëˇē†0*JQNž$O*ž~Ú_4=ĩå˙FÔߕ¯‰?ޞ˙­Į˙_éëdĩÃõN^\D ė|@AŅ8’.˛ƒ˙ĩWT7{Ŧ2ņ=¸ŦŨÎK¨ĸˇNÕŋÄK”eˎ LTĨÆüF›•ŸNf.lî`ŊÆdĮÜ(RŸ—ú'Í{|˜ųŋØ4ß"hŨn€ķM_ī•n…}ĐŠih‘­ęģĢAĄō"‚ÖŠiéiéÆč-$ĸęŋĐĨö5ēŨnšOC˙ĘWďĮY‡Öß×­zēN7öũB€h|Hü_HĪúŽžę‹nîfŽ›ŋ՗7[&Nū+˜:w7‹ũžēË1ˇa?[¯ˆ“ķË`˛XčđŨYq ĮRĄŲå(ŸüOŋú-åM4ĮTšRüČü‡ŋ˕f÷Ąíūâ„ēËS)5ÃU­ÉaōŨLÔ´~KD ? iM3VëtÔĩūÛōuņņö_áõˇõūœ°‚ËÕYWÅØGå>$~'áŧû´ČAŪ–ąŪžÍĪûV.ž YÚ[ZŲ1{?ˆ€BEúŒũeŠ_u;ÚŲk—ÁČqáWÔž(ûQųn;Ō ŌQo”´)i=žDø-DŌŌÖŧqŖ^vĪMãļã„Ge:SX€Ilîu.éō-\čËA÷QŅĻ­ÖéM˙‡zĻĘWSâĨËC„Žœ. ÁėzËw’ļQĨø‘øž‡—ôũCY DLY•ˇ‘fƒ¯lŽŽí-“ˇmņ`¸Ī†ĐjÖgÅÍŪWIcŽąšŦ|8œđZŒhqôæĪTŠN(¯”]Q?-Ķ×ĩ)Ĩ4Ļ‹čą,*QÛđ2"´l “PžīöÜze]Kkt8Ō¨ĶûFi,–čĐņM ËtÛų'Šŧ1§jßÉiš”ųÃÕĮIĮՐ=›ÁÖxÃ_ķeIÖXõ÷יųņ]Jø¸īúÖÄ=gXēŗ?g–ąéޚÕÖwKm€ÆEĢ[›uš Q\b:ĢÂīĢņ1.Rkėĝ;Ԙėf'-ÕļXƅ—ŠęÜuŽ7/_>‹AhÖ¨Ķ˙B€ îļĸ>FŽ*Ú7‘…ŗ }ōīŌVē ōû/Šwķ §J5ō’ŖiM%IMæ‚øoē›ÚCꎡĢáÅĩŠ}ŋ׹¨×m­]ÔJjEŌ˙šG†ÕtZZ&ŸÍj€ĸ<h{Ŋ7Č ÖĒÖŪ+¨¯ą­™i6ĄÕÚ+‹eTj_$WúR÷Ž"W<§V˛IÜxŦâ6xÆ3eå‹ôķ@Uí퓷`…nyZ*E¯;…||ˇĒ߁GÍōop<*‘ ÕŽcQ]­´kƙŠ6Õu`ŧž™gģa$Xrkȧ ƒŠ'Šqá“ėŪ@¸ĩ†˛ŪâÚÜĘmqŒ˛N⠊ūa0—’<˛ˇ:¨îāz˜ė$O%]ŧvv˛Í4Ŧr*9%aj­ĨI*ÖiŽŊTļÅÚ[p•tĻĄM6õ\ü Ģ™š rN âîģ u.Đy§L¨5ąģSĩcB”Qˆ;(Qëz2 LҚ„î ‡…H*7Aj:‘E ēĒ1ĐMSxĐ­ĶRûbŠ;웉ž.ښHäŧU”Ë:ȕh8G{s/s‚ČÖĨbŽđ #ˇ‰žëŌĢ&Š[7*fЌũK†ĐŸoļ§÷6…#)'5ž‹qál;íŲ˛Æĸ™X5Å÷ ŗž-.EĻZ1DƒģVÅWˆœīÖ°k#äKvéĨP.nøÔ—$­ÄĖcˇ—kîÜdXŌá„ŅČ8f š("`c“ė˜xpĒÖf›Í ĨŠ=5“š Üž.¸ģ€ĸ‘kΞ2Z00ŨDžJĐϝŸ"jV¯ôNĩNVķEÆķę5ü,­ˆŠ¨ĪžĮ$ Úē•|XD‹Rú’&ņ ųsQŸ2°jš@‰qä*|Dą2ÃÛĄŊÉålcíÛõKĖŒģË:VFÎ>1ÆjÍ šK›„ĒÎ(x^\†N>uĶ–ŊšfÛs§FšˇŠŧÔ)Ē˙Q§›‡&e$ĨĶ“åa?]öŒ‡œ¯ÉėūųFÜĒ/yWBŲĘ4ĮšÜø: Q{‘ēÕqÕ _“S7É}˜ųo´øˇä(4Ū+•KÍG8ö:Õ΍Y/ĻSH+áō6ęg{1ŠTƒhŸIĀãøˆq§äÎ$Ē,uk/fë @^ÂŊOmÛ3SҚĩ[ģ mŒąJá^+Î< ­FĢ +ĩŊ͚ h(ĮÂĨōö+Ģ9=1<žUö×*ŊÉA5!$^jęō8MğOŧEHÛmĐųZĖ$ąžPBŸUˇōvųÔqņ¨c;ŧzļŲ­ Žæ(Ü8+¤Ŋ aAkÚšS5—*cMē_U2PŨEîįĪOÉ[K-Ô[ĻļåW°4fŨ5šˆ­Z!īL…înT,w_u™úrî–ĨeUˇ#IŅšymīąlfK,jw˛~YMŖš“ÁpMXÅRˇ‹ŧ_ę&ŗlídŸŗ ŨnK Wo˛œĐ×öĘ­iėĀRûëÕg4’vØÍÚŠVŽå.Ōļ šmģ6ĮĶŋW?07ˆëĶ5ĩŨ•'ŪČų*ŲøJô>ëS˙ꂧ•R;M3™*á‡6o-æ”}[g }0Šo%ŧMĶx­V¨ÖĒSĨ÷Ĩ>ŠOŽáĩQy’å´’-]øW>ģsĘ%öĮ(ī]Č#…˜pO1ß5bxČīAų< I"/lwn×=ˊdöŽ[Ģž\ņ‘ķŸ˜Šæ#w]ĸē45Čq5ãŌmįērd™ØrõG/rÎëÅ{ž=¨• ĸž#ņQŸLT’ŪâÆÖé˙fãYR‡_7Đ­ŽXĐ6kŽĒųĒ ÷¸ūY<ˇʸõÚ#}͊4”˙vūMKī(ŲãĄâ‡ŧ‰É”x–­}å<ĒSWjŸî°%•Ŋ+Œ÷Čšyœ-ĪĶŋ'Q7ÕodŒd•4Ŗ\ÎņŅWs˜āî?6UbĒæ1Ŧo‹{ˇíĮ;ČÍ#Wãú`đÛ$C3yāŽX=:VÅn=BÕ(íÛØs/Q&ŠB<ËâąhĪpĐ/Ú$ûĪɤŨną~VFŖöœîM‘đ@,uē;§û,Gԑ†ĄÖå;Ĩ_–¨ŠãDT†ĸAĻ_JW*VŠüÔ`¤>đ¸ØŊ_ŨcG™+jQĘI‡‹`x\ŽÔĮŠUG-Ģ¸§œ†‰÷[)i“o[ošpģŽČō̞*Ėũ ãî¤öā%ĒlOŅļ>niČûņ¯jˇmÉUFr÷3TĒ éÔ ‘*eÚĀ|D9Koāš Ą&Н¤1Ũ!ņܤĻÆŧėžTŪ3ē <›4,nuWYK\Ko ēÎĻ4´ū+Tå?/Sjž@ĸōëĖŗē  íV ŠnWĶ<ŋ†Æx[Nū§Fy Ž8#ž_L­ē°cú„"¸¯-}9䎛d7(ņ°°† ˛LŨÉQtĶq4å„Ŧē{ļĐ*ęŽFQ˛4~ŽŸˇ 5ė}Ë[öa!ûZ…j›Ü ļM:ė¤h¨T™LĨT™än%O*eŠ“ƒ[yV:‰TéĶøŖöGöŸŠVŖ}Öü.ˇ0Õ!ķ!Јn­Ķ´“6…ͺȰ´f$ĐČ1 ht1˙|SˇęĨ“Uysē˛ĩ2RB=č°áoíČI ]\äa—œ( “Ĩ•ĪB6z°^i•Œ‘ģG1•.š2sŦT=ĮŠnR3ŗ,BîīˆŽGaÛz$™š7úe MēŠE!—LŊ?S]EnąM'Š:Ļööđ Y2’§éH7#"zĨ&šųYte•.€ãj~•ŋŗyiB J‘äû)К”˛lŧ>+t>ëēŒl¸ĪŽm*ß]vpéĪŸŌˇšt}Ņô"SúĖĖ­؂ōEĨGôĶ/Ö?É7ĻdĄ”Åõō‡ĶŠ„¯ÂŽŽt'¸2THÛŅÔ'J%7"‰d‘ÔI?Õ[ËnUŒˆ~ØÁEO*F%¸i V§ģ GÂį\o9Škũŗƒ•H~ë8Œ’Ú§nmVn-Ŋīf¯¤>#ĨxčūŦ‹ÄI ŠŧjãÚãyY8Z¸õ mˇ¯ũ‚î—Ü2}ąÔŸp*Iæ2{qRã"ęŸÉOäŧņkÔģ…}=ŋ°+tNâME‹?õŖ_¯—œļHÉQyŠĸ6õ}ŌĩļÔyە-l<Ä ŠŽ_šˆŽ;}Ƒ~ĨfŊI$¨ …7 OŽäüį°JMŒzŽ>šäĮ3"žĪËU ĩÜ-¯ž*!­Jk)2¤wŠ!mķōŨüÖUW’EZß*/W>#mivŽĀh/„“˜-¯„˙ÖÁ!M̝ #:(Fe†M&ãįTŽĪJŪ•m=m‘$čZŪÎÛŗ$WšŽ|‰}Ŗ›Œ`÷ôė!ėh˜ÔcŅw°P71Ŋ§-ÛúšÂ¯fAq™ģHâķ4ĐD*%]rđIäĖMbäˇâcÉ7|L×y$-[+ŗĘöÉtuJKŲ6ˇRmŖaĒZ֑HĄŧL]ËČî žž+'xÚšõųjŖ]bÕ`TLäkĮi„äē’ygö Ú/Ē%Ų€y4žj#Åd,ĖãTŠĪ뛅yKU^UĮT_ŠÆ$rũĐ-¤’:šy\ÛåÛ‹Ė“/ĸæ@ÉŨGŗV>*å5Ž×ĢģđŨÉÆHĨ 'ĢܒzÎäĩžg—5í[AÛ¨ö¸MÆħÍZ5¨ˇšĘ?Jˆ˛J¨ÜՒIõiúiÖâ0Tzš—Ķ Ō+ ˆâÜŽAcĘ­×lix•xWNëõ’Ũņ{Œˆq/qŽff.+Íj•u[I$[3 ™O¯\TŸ/Ž)áhųύŽ}Ѝr ŌĒnMöÄŪà šnAv¨ĘŠÎĶTģ¤vŠKIģD>¤š5‘a&Ų}Vã”×Cé)úvëĘæoDw|dŠ ÷ŲO)Q?),ה7vū0]i|S˛q@ܤ’CX¯qúEss-Ē´ōē3{~RåA$q¨Mr &*é}wrZļQÄnQ%“uĶz/v˙ZHf¸?„ĩžsqiūGuÛsÄÃüļÍë]ĩ3ę™ũoę¨ÛÉ÷o ūá:ĢfØÄDšŲ-"맆‘ËÖļÕnŋLŖ„í¤t¯%¸:¯mÕũ/SGšq‚Ą]ĩÁõNH§EŲëŒ÷îIcëÆÂ^7‰Įn(Ö>š.ĪĻĖũLsz¯×͡ŧÛ­ú헄7Gô×&åˆT,ĢŖkh(É$ßmģ,˛Ëá‚ķ¨Į¸:Ģ–åŦw/’=áū$n,eĐUaļ!ģ:“ĻL’Ô‚>yKĄ5ktž]ÎÃ}ąxkUúr˛đ“Ę’ˇˆyk”*Æšélš“Ú3ÆŊëØER˜ūî"‚­ĩ]ķ–ĩú{v‹ÄOĄ74xö?IBĢ?BĮėtn\׎Ô?Ʉ=ÁúŅÍmâŸäČ7­ GĮą;0 sžõΌšíÍ)Ü˙H—K~ģ ioŅŠį Ŗ=ˇÔLžms÷ņŲŧb Į"­JG!PøˆQƒŽ†ōˇkëéˑo<¤o*íęĩō‹ų[؍Ŧ#Õ#âPŪō”°Ļ;ĄËl Dî­>øŧŅ_ 5DúWÚSĒFŖ!Ģ~ORHwxT¨M7ŠmKvŨTJV#=ûyĮ ZC'œîĻŨ"]ŽČġ¸>¸ráCD~•˜ô\WĘą‹SŨPęáϐĒE–yœ´ˇšŠø*Æjo,)?€ûÂÚHø›0¤ÎŦRQ§müښņnT}ņž×^FˆŨ%kËoH4ĩđķKXįŨDž |'ķHiĩōí1¯RÛHŒVŪ>&äōž/âírI‰Ô ܖG )Eˆ„žō/q wM$HÛyļÕú%]™WokĢNÜÛ¸ÎōũöIÂöøs'ŠdT§›@\7)a˛˜Ä˙ŠáŅ.›) ņs’Â?ÔKzĮšb[ŗå¤ŠÜJܞtÚÉōŗ^Vîx•ˇOE͆9 eZsĒ-A¨š}Öŧ[ČŦފŸEŖôÉęĸû':Ž?å“o@LüZĨ¤~FOPËHĮš‹˙"o7Üō/sōƒ!äŊ*6Ղ [í} HŪģrĨ‰>->æ›ĶíĒžĨ^J‚ë¸Ũ|á\•Áōmŧž^“5!OŅ\}ōVĩVĘH-tĐĮhGŽ?.l´neaËuēŲŽãŠ†éÔÂë3_%B7VūļŅ ¨SmpŧéŊˋ›ĻÛčēė iUf~õDŧAūHŲP›3GŗyvΚá ō¨´é)ŠGPú‘I°yēĢâ›ę^kģ(õ[íäšÜÖ)MāLäTR=ånˆDוIīsÉđ¨ūųmHkyb…õÎŪAĻąn3Ãy6žû ųķ-^*6ԐÛuw ĩErČVˆĸ<E|đņ |„ą•ĻÕYJbŸ$wA<ĔįŠÛ7ĸ-#Š%ŨI/–ôš~4ÃÉŲ§čǐ"˜…{gúÉ(Ī_Fü˙ڈú9vŗqbŖŊåé=2Ã(d™XžāŽb˙QdÜŧBŗIČYäĶ^¨—„vĪÛ7ņs{•;˛aÜ3ų‚XžIĮ •BMąBš}‹jEî 49;O+ŌŠ,čeąOå}ŠŊMOaæsąDrĢ}Ȳˇš 麌<ŧ›ôÜû•RË ĢR*ƒ-€P¸ũGúÂ!•ôŋÛ$K(• oŨ2@‹Q.ų -™kģ㖑ŧQjķR¨ xŸ]Ō”ė UĘT3pHŽŠšzČņ3ēúX"5įqļœqąÛ1É)×6g•ū–<+MĪ`žMf¤Ķj[v‰ĖŌ? m›ŋg’Œ‡ˇÕwøÕš=§ Ëũ#? î‚ũ;Áꔨ@Ũ°Y*ņJËnÜ­ßĐđ7#x=xņĻí7隨÷ßȝÕe-ČĩŽŨŖîæGcHū¤ķrČĖ*Û×ū–åÚŽWĩ=Ė^†ß#8F ĩ'†Ē/å}~ĪÕ¸ä2,°§qŒzĻûn<"úÖåHĮ#ë3žtĢÆiS鷔Œi˛,ĖÉü1ŽBՕ šXÔ°}@Ņíæ %ūb5Zũ4â[h|DßFÃ/8ōÁ¸Z’­ÅŒ¸æã.Hh§ŠRŨ7Ôrŗqmînįsœhüæ‰ģÖą1ī^ųŠ+z,÷WŅ,đF{SJģšÎ3ú‹ĶĘ㗠Ģ˒˛"*ąLŸ&– ~6ÖeîŽgí¤~™./˙šíȊРŒ…Ė\%ĩa ¤2Zø{¯]×kŽ1…YĻãéDœ™‡%AÄÉrIáWÆBĮ)äfōqu#/ T Ômæn+Q"E<˛KČÃ/"ŠVzui’Ũ‰ŠÁįjŋD ÚŠUYjŪ1]×y¤ú–ԓȂՀĶC{šKvâ‰Â9ŧF—VđIwq#\ZGs3öÔ$0ˇj×ģ'ę.âg<]ÛÆ ÜbR& G´’˜ŪyŠ!s4‹*ĶģfÚĮ÷ÜņQ{3K4“1¨Sˇk|ՕĻ1vŠģtËĨTŠ#RØFļ‰(ĸŠ ˛UĒhX/Úwē íS$ĸ™Z•M:ÖĒ3æaÍTŅļĒAÄJuQŒjtašŗr‚gįgožFBWąpĖ"„Ē–­™?Mí‘îK"b’)%žâĮĩ¸’ØrēâUb r–Ą„ĒbGH;(ŦiەšļȎšåLŧŪoJÄüOhr™Y‹)eX⊠ŖIpËLیx]-ÁŽ+Ié3úfQ°?›^%Âĩžé{v1g2cöŽŠ¯ÚzĒ…§S ‰1]L̊ģũD,[„hÂ;”Đļ+"ƒÉv_DÎJčxļģĸäų+Ģ[gƒ-CÆK)zö]]ÆÁ:‹! Ü]Ga Ŧ=QyovũA‹¨æ‹šLˇ†ũqŨZ̉ŋ¸že> $ôTpIFšūŽIšŪÚ^ė‹@iōF‘gŦVŪ×# Ã"mox  ˇ/*FŖôɔ–Fš(BļŠãå31%äp[C>k#RØõRŦšiWÕ2¤ąįāŧl?T1L…ęȖRõrsú‚\–RæLgT„ĩļę;čRúęÎånīīåũˇĒ’ĸÉI ÎC °Ėļ=Q8žLž:ē6ɲ3‰Åę¯1÷1™)|4œKGĒS$JŌLüģŽéĨSČ QãGņS{÷=pĘŗ@ƒ¤˙ŽüHü_ÃÁŦÄŦ,@e1ßíĖË(l, Ę¤™îŦÆĮ‰éKÔô˙R[ÖŦxŽۏÛhÍž*˜›yŖIĸé)¤ŗÎŸn‘ÉO•ĪÜÃßĀõßõžūˇņ#ņyö#Š…-ņˇ9D‹;];a÷V]Ėļ֘ká’ÆõÅĸØ_õÃøģܒÚåČtŽ.žĖ^Œv6Îqug_ØēļēÎöî÷)‘S5üQ¤Qaōɑ¸ø‰gXãŋ™ūņ]!kësųTÄÛÍMG[‹N¨•aĀäOņ%Ž2Ę (2Š&{Ģ1ąßâ~XEúLöQ16sF“EĶ.øū >Ī}w—ĘägfŠ‚0–2ļÜvĢ€M<<ŅŅ<øŌ6¨ŠoŊŗĩ0Õw4s'œ}'ũwâGâūūb÷ü>ƒūģÖŋâÔv‰ģ˙áŊĮ+,Õŋ<ūf^Î#§ĸå†č\ŒVRdė-˛6š¨ēĸÕzynz™žß†˙”¸ū‡ŋëŋë}ũoâGâú‹ņ–Ÿâäŋũē+ķųßÂtGõŸ‰?ŽÎ•ũ¯øŧį÷*éOōúÛúÆđ}kųęË˙xĖŠį}mļēÄȒu-Î}+øüĪ÷ŠéŒ¤ųl…ļF×5TZ¯@Ī-Îs#øūƒūģÖŋãÔv‰ģ˙ ŽüGü'ü×)]%{.CĢ[í锿ŒĨG ¤™­J[i*ÎŲ‘^65ÚaF&úzOd#—R°îƒ'Š‘‰iØj˙͡I˙]ø‘øŋ‡?–Ŋ˙ ˙ŽõąÕMem×÷3„K~€šėįg„I/XËÚéĖ(zwĨ06y{L”ĪĶ6Šy.2$‡â }ŋ ˙)qü×Öú?úßďÅõœu§øš/˙AފüūwđũgâOãķKĮ‰ü^sû•tŸų}mũcø>ĩüũgS­rČ%ČŲtĩĨ”Ũ=—|…Į]DĶ˜īĮæŧV/g–ēÉLũ3h§’áĸHzß!ūA˙]ëcĢjšĘÚ˙¯î:g–ũAéīˆß„´Åc Ž.8bëöûzbDRdđĮn&•)šd’99¨ēŅ7 Ę9‰î8ļũ#íCĖK÷ˇšŪÁ W'vŨ'ũwâGâū~^÷ü>ƒūģņđ=Ô6—öÂÎŅozˇ?m—O[ß~å_&ᅊ!+ĨrI…Č\AË[ß_ŲØE‰ĖÅ˙.FW\}…Ĩ‡RÜÃßĀõßõžūˇņ#ņy˜‹azs%oÆÜ_īŒÉ./ĒbžÎūũ.Ī;|3ųޤ˙øŧį÷*ÄeĸÆuD‚Ī%iqucĩÍåMūfÆîŪöˆ‹eÖ\íŽF {;;):ã5k-—Kä­īąwļūëX,ĖXÜõÄüĩŊõũ„]!yúū§šeK|Į~ßņ#ËĶŨCil,í÷ĢsöĐŲt^Zėxccĩšą“§”Sa+‚Ũ6Raģ. VŖĀĸ™~ی4.ëƒVOØ*\C.3I‚ˆV:$€ßZG=´xuû \ę D’acîūÍaCâI‡lŧPâ⏐aV­Ŧííä› ûBņmÔ8ĢTGÃĄ’ßú%o…Ë]uŽ2[[ aē‚ËšēÅ[M/ė5iŒĩ‚¯Ą†âڔ‹|l -ũœ3F0›6”Š~+¯ruSíĻXWé÷PŽÍ=“!ŽGävJō>éĩĢąē’MÛĻ™KJSéBŧĒH‰üã̍‚ĨČäŅĀĢ,ú3>ã†7BŪÖJ†ŪŨZf^Ɠ^(åęb]ŠũC4Ŗ•E敨W™e ’ĒˆÁŨRE–UŠÍ#q’4ŌĻŨšČ≎TŽßZîKŨPąIÁ‘ĸü\ļ€ōy âZžĀÄō \BËy SN--…}ĩ…v;zŸn’¯‰×i(i<§ĢšĄ›ˆ šŅæžĢ‰q2ĩ+s/‚Ä ˛HŽnoŽ?âšę‹›ņķeoü_?"ßXæ1+Ž’9m˛7Û-–'=‘Ž^›ęoŪ&ŗˇĖe¨ôļx ´Y[ĶV2ãą]ŒŸ)ŽÂH×rÛņ ŦËB&i jāũ5ŌH{j/IbY ‹ ¸éËzõ]“N` E9ŅAɉ`Oš#čoÁZ[Ž$…æ‘–¨=Ūáę>=&áWlÜ­ĒOPōiFaéū~­ŋŦbÛX˙‡VęõÔš1xțœBƒâ tļVLĩ§Qß67e7ę,úŨ8u6{ëä-áŽŪ ^RKŧĮUĀ— 2÷,>˙‰Ÿŧl~#pnņŲ|ŖXåëĨl#\ö~õņ،tæęÃ1d–ŊoW ĢgŅ6‰mÎe$°ž–4–8a6yŒ<+ÕĩĶGËXuõ„s5ŧ1ÛÁ‹ĘIw˜ę¸ãŌww¸NļžēĮâđ֝Ģ5MםšZŒąS%FÛĻ…Iâ”Ę&_Of “Ė|ĩJÜĸ@{Ķ)ęY|˜Ô0į\ø|­­ÚRŪVĨbۅU$nĸ_#Á˙~å—tå¸Aæ2æßm5Ųĩ"tÆK‡†ëoëKe›ĶYĩŖIÉemQB­Į÷ęøoøžŧūģ‚ü']øę ŦŒ—8Ģū§ŋ\f1lîs˙ƒÁŋgÃ_ņ:ÛúĮMūĢŗWM›{ú_ĶG“ÄF™Û›kž§ŽŦËäN[Ļ?¯ßļ5÷l]^ÍõWInŦvg%†į=”ēŦU˙Sߎ3ļw9˙Áô'õŋˆ˙†°×é}”éÎø¤Št‹JP‘Qy‹áŽ„ģ%|(§}TRGäĒJ¤~ĸBŊ(˛fŒl÷{zxT–×’ĸ™tUxmOŠmhÖšŖ‹}ĀųíčJyˆŸôŸ? ÖßÖ1¤Œ_Ãt_Đu­ÄÖØOņnŋWÃÅõįõÜá:ûķˇ*'X[‰Ļęœīá1ũÃ_ņ:ÛúĮMūĢŗWL°ũĮ­Ŧôî7&;mkmÕÕŸéf ĶŨzŧīiÅÖEb‡Ē:M‚õu|8Rļ=líë q4ŨSü'B\øøkœĢB/¯æG& ū[‡ė‹`ė>é&$ÔiRy}ú`ôĄ;Š=Ô×>&đ†¨ÔúA+SFV0ËJŪ–ßZ5áh,tODjŠ5î¤'Ũ Û{¯šâ5ÍRŽ&w´č+Û;lWWä,'éÜwœgFå`ÅˑËā˙IcđÜ_ŲĩũÛ]{gmŽëKûđ8lž90ũgqÖs.),:ƒuœ'î9œž9đøŊˆ>ŪZ[Zõ~BÂ~ĀdąŅázžōÎ^ĄũÛPfÆ7Ē#Ía/`šÎáŦ SÕģbë.cšę•ĪŽ-NK qYލĮYÃc’ģÉ5ö;Š1Wqļ[ iPæĨÉd,:ƒuœ'î9œž9đũ𺎞ž˙ qEßX[âēē⠎Ĩí÷ i$uŽ(ëZĻ”UžŨ¸ō$hļ§mQķ3_ezÍ<~ž^xq‘‹‚kũ˙ŋöM?•ÁņR:Ž@6øÔ‡rƒēŒî?`Ėu1Ũĩ\MŦΏŅFĩqkÂŽ"ۓâmĢm 5ĩ\áŦŌ1Š€ŋėĐՎ>Ú”ķ2ãíšŋlļĶãm•­ĸ ?hĩ–_ŲíE>*KŠļäØëmÅoiû}ŧ…1–črŅ:.:cˇ†đĮ"É‹ļ •ŦKĖŸí–Ōƒ‹ƑAûmģ'íÖÄ.:ߓYÚIGĸÆÁ+ŏ†"NĘl–™V”ÔįĶLj/n5/ˇûzzŋQÕA­“IŗD€FģŒ=[ō‡ĶOFˇHē§>[Ísãšj÷¯ũ}‚2}­Rč$:‘§%*ܝH)|GZÕŋĩ~ø<Ģ+S{ŨôäŒ%ēUŅTÔdS]÷ĶY=­Ķg˙hĮ)ī<Ȕã“q¸ķ$ሁÍyî@=:ņ8ĻoD›ä”Ë7ĸ+eō䖑ûj““āėÔB¤:_ũŦé*fđžL‚Ĩû‰ĢaS RxhZF˙×*˙Ä%!1 A"2Q0aB˙Ú?ņ~‹ŖąøV+—›ōŧĸDycīÂÍGõÄ[ž NˆÎΘųMGÎ6ē˛2v1,ŧ¯đ~5‰!,Pŗ.c?Ú'}…Æ ­“‚Ä_ՑėŪÛĘĘņ¯4†„ükĘ’ģ“Kö‹û}ŗĨú5_Û1ėRWА×ųQ¸M1”IßÛÂđåáD•Ąi@ŒTehÔĶŨ+>&S4ŖėžžãâcŅūŌĄi Åá ”ÛF›{˛Č’#xöQŪ(˛íåŽ|dE3vjÆ6‘Ŋ •†nÁFœI2ÎĘĸLXO‘ͤnbv„4%ŠđlzŸÁČģ%*Vnmō[O‚„#QĐģä}IĨo'ĸsĸ<’d]ŦQI4ˆjS=Ds.MĸT„WƒxDábN$•_RqāØi¯°û,ÜK“iD×Bũ’\‹“a´J–PŨ’ŸԟūY&Gėeķ›ŕ…—ĄËŒCõEh]cØÆųeĜ¨Sfė$N<áwxBt„¸ąŧAđz;ËΞBDŲ‡†ŠųB?aē&ŦLģę7bBB;=Ķ'Ip!tH}I}Žŗ,1ėDÅĐÅŲđ‘5cÄ0åĘ5{eÃvKĄŦiž2‹/‘Č΄Ŧ—E\‹ėĮŽĐû:Ä2BV·+čh˜ˆu†žÄųŽ/’ÔežČĘFՄ„2oĸņ¨ø †1ÉžhŒpēĮąĘŨDŲ!“ā,—Cīī ’4ü,ēąV@™vQÉvV7‰d?#&C˛&Ą"+†ä|*åČųfŪEŲcÃÄDZ$XšÆīCäJ™gŦiĸ}âėŦ.‰3Oķ‰ŦGĸcėˆÍ7ˇ“V{˜Ä.^%ˆõãģŅÅ T3ŨdŽ™ėDĮиxô2 ĪDĮذŋƒītiŧ67‰bÃpŲm#nĸZ>oø|š‘$A KđZĐšÃtKĄ ņ‰t.„ÍÖHŧ2cčKœPÄņĐ˙˙ĻÄÉB˜ôš>1ipJ.ŸĻ>ލŪ^äz.ĪLEaņˆđ"÷Ddĕß:tMŅ˙Iɨ¨gąžN–t.N"khČ#Ų>HŽJûx1ôB,CŠ!*d•n¸5¤Kģ$Ä>Q´]meŊÔ(ģ7 žËv5e“Ôr.Ņˆö>ĨŗŒQ"%–{Jĸŧ H—(Šû|á Úp„;l]ŲLØQT…mãĄ2ŦqFĶhâU6m(yônfæFtÅĢ>X‹QÔRtJqL–ĸô|Œß/éž_ĶJMöOUz!¨Ķå‹Z&õV-tGR'ĘŦz¯Ņ§Šk“损*%ŠbœQōDųQōF…Š=D‰jņÁ G|ŒÚ$ũ–ZĪĸčŸ~Z+íåËđ‡åøĨôįr%v-Čåĸ¸xdŧ´>PéøE}[ņ1küāéã•É)‰ÉœácĐâl"lB͊>NžÅ|Q”P´ãü6¨đŽ,ŽœOŠ$UĒ>8 N&ČŲą>ĒčŲj6Dڇ§Ā´ĢŗjčPŠ%!GƯ“j/ø^(]âÍF”š˛B×BÖV=dFiĢ%ĒŦßgÎĒ™։ķ#æBÔV|¨éYōŖäGĖ)´Eß#"‹ĸŊ‘Åķš(ŦŧČFģļiĢ~)ũ4_á˙ŸĮąž1vIŅØģÅųЉcX‡Š_W>üĢõņS‘^ÄŊâ(‘yeæņEø5}œz)Q˛#„OøˆPV=8˙›PŖ“fĶj=P՛U›Qąaą Іė| „G?˙Ä)!1 2AQ"0Ba#3˙Ú?C62Š(d`4l’ø6ļ8ŋM˙—­–^ˆ]idIđ…+>5HÄŋŊ%×"ĀØđ?ŗõ˙éá¤`_Íéi —ščôZ!iáB,R%1HŒÉNËĄËŅßŖčZåu~<åIV“W8Œ”Ö>=UĨz(H­T>͉“Æâ&7ĸ7­b÷ }æö}šĪĄÁ×d´oĐåz%Á-#Œņ‹ˆŪ˜ũÄģÕĀE %h‘E26ģ?h–{D"ŋ%7EŖ<ø¤bˡį‰Éû%žú%ų*Ē‹ÖĩE é[#‡vfKfŠ Š3d¯äDW68ÉNƒoEčĮŅ6#ccMwŖŅ+#‰ąād•wĸ;2Xäˆ:b›7¤!EˇH‚ŽËļEŊÃ"ŋƒjoƒoÕH­$õz"Û+LĸVÂ(FeODčŊ>4ø:EôcëNãE^Œ’ūDŦ‡HōcŌ(‚Ą˛ÉŽ4nˆIąi—™ė]égė­ĄõC1"]fWũŊŖ"éi Y?i.Éō„I˜õd´‹ÜČĄ,ÆErU‘ėÍŪ•\ú¨‡JČžI{…ĐšcāDD!ģDD¨†ėƒ“āÃЄLDUĶ*ä~¯ XŨė}‹—Cūu‹#ŅØĸKĄr¤v(×bdL™ģƒ B'Ĩ˜ŅGÁ—NƊ+ŅÁ"×$ŲÚ(GÁŒŲķ§Ā•+$Ėq"1“āɤSĶ*t.™ôZQZ=,Ÿf2\Mđ.‹˛ÁąčûIô3‰=3K“{|˜ãŖdē*ˆžDGLŊ”Š"YĮZl’!Ņ12LBú:eéōQ?pˆ1˛萌ĐŨ#6đÄË’ėŠŌ:e/Ob$ĮÁ'b¯NŲ)Y&.´ų/‚]é$4–“îÎôų'ŲŌ+‚*´Ž“\jĸQ(ōIüiä,ą –nā‡lCŅô|›…ĸėˆēøf7ĸ&‰t]"ôŽŒ—z%Ádéģär/GĒÕCzžÆŒl}7R%Ļ>ô´A)Į‰Åċ˛lĄ•"Ī‘čõÚYĘ*´‡$„Æų%ؕ‘ė“ãKĸĖŧë¤}Å˙†įBvG*7ŖĘFJJ…üĄąsĐĸ%ĸ+Uĸ‹–"\Œ‡ —gÉų2;eŅļˆŊČĖ5üŅD}Ãf7cäëĢčĮŲd}ÆIQ|Dē.‘šúkD!ĸ$O&‡ĘHʉ "5L\2ø%üËr)mąÉD‘đ(ĒXé$mD1(ôlŠY‘XúäIŸß׊wĸė˛čRä“äNˆōD}#MąÔY&Q$3…Øé*GJfî[ĄŌZ.] ¨ŪÍÆá=¨ÜZĢŗĮŖĮŖ&%%ÁáĐđHũy“Ã(Ģ#†RVG?ĐąÅ#ôlŅš+Š!Žģ2A8đ‡‚BƒŨCĀÉc‘â‘â_&Xm| 4xdČaĄÁŗĮ"8Z6JĪBÆŲ\ōKŽn‘E?E}žŦūĪTũŅôOŪŊ2ú%ū9ēD)*&YeHŽLj—Ģō^™ûŖč›ū×Ļ\M?ņÉZŅŅ#ˆ‘›G•Ņ埨ō=˒yeGí/Ŗ&G‘ŠrŠāy$ųäOėķĖY§öK#—gš_cË9|žiô9ķcÍ1åŸŲ៨˛J=œĨŲäšų7É;ąd˛Jû#™ß'–úInIHRk„NMrO,„1žË6č„9S,Lø/‘ŗņRv~BJ6,’?ZV~Ģû?ZD âčũi2XĨÅøĖđ2X%cĀŲúíŸŦŅúėĻåG‹ Ŧđž(’[]‚ ×ōKžEËŌ<éÆôąŊ_CĶņzgä{}2˙č´üoø˙×Ķ9rXŨŠT+/‚&â=Āģ7-,ąécvĩü^™ųßLŋú-3{}WÍišy=3Å č} qĸû,˛(LčZ¯JÕMĮĄÎRėS’á3Í?ŗÍ?ŗË?ŗs},žÍō}žYũ‹,úąĘT<˛ŽČÎJ#›ėß).ÅŲå—ŲKāß/ŗ{%’]YļĐŊ ˛"+‘˛Ī˙ÄO !1"AQaq 2Brs‘ĄąÁRt˛Ņ#03b‚’áđ45@S“ĸÂŌCPŖņTcĶâ`dƒ˙Ú?˙ņiq€ŋOK÷Âũ=/Ū uí=‡ķsQáŊĢôžâˇk7āĨî í+ôô˙y~ž—ī(éĪĨ˙WĖapäĮ¸ĢôøĪŪoûWéņŸžßöŖô|mfžĀ(aqģÔĪ’ū~dÔã MĮxJ­Lĩ.Ö7R:Ô} žž•ߊ=K‡wvaīT|Ž{ō™‡0ęØ:/Ķã?}ŋí_§Æ~ķ[FŽ%ĪfXp(J™w/úö Ö7ã˛…Z4éÔĪRÉ5ãÎ…O:Q ´0MĘ!ĪĐ{T×đĪÕO7ÍfÁãÛPŽdTŧĒė;ŗtf[Ąė_֟ö?û*žPƒĻk/ëOûũ•31=1{CŽæ^1ÍQ¤8”Ö7Fˆ ôjΤ]RåĻ4TĒ\ĀV Â8wd¯IåŅ:…Ōģ į’ŸāŠâq• J• åŨËœZīJy×°^ąŋ˜_[ōT}Xø*ž›~+ M­‚XîŌ°õ)ScúJėܔŦ„)Ō5ņÛ ĩŖ„…‚tZŽAūm™?ģkGē~j•fˇ0aēūŦ˙ŋ˙Õa™KĀØŠM3_r×O\NŸÕh iššŗ‘Ë€Té6Xl?÷tĀ+ ‡Ÿ.Ąw°FŊ,e͍¿ƒQÖÄĖ.ŗj— ą'^ßú.Ģ_ėø/L|vaŊoÉQõcāĒúMøĒxz^ uW6­ĪųUŒÃSĻéũ&w;ē,ĄaĪū÷ČŦ)âÆôgēËĀĩyb ûËf5˙UÄ{,ŗT=‹Ŗc^ ÍÂÂ`ū–ŅY´šŌ×/Û˛Ÿ„pîqmW,]ĨÍ'ŗSîŲJ tŊįų ʘü+Ú-ÍYRЇ­Nŗz6ŨŽ‘Įū†]ÁX,ĢU¯ölŦoĮfÖü•V> ¯¤ßŠŖ’›EW°ŽâJĸĢQŲ[<:ĶORÃú˙‘XŦ)ķ;˙đ°nūęžō˜ŧGÖtûJÃø/ęÜø XS‡ÂĐĸKĖôtÃeQqÔĶi÷&UĮĀŽ˜‰hûôŲsŽÆ’ße—é˙ÎĶ99ū͝æZÆųÅ5ƒ’˛ęâē•—FOė¸/XߎĘ4ŠÔe<¯—&°hŅ´ęęaŪ44ÚGąaۄ`{˜ûËĸČKëūEt$ÚŊ(˙VĖEcŖ)šŪäķĖĒoäU:Í2×´8,7ҍžąmK†‰TéZĀ„ī>ŽhęøŦV+ëŧ3Ų˙”úŽŅĸJ¯\ëQ÷øŦ6&ĨlPuZaÆØŋr§†Ã>Ąah;æ÷ūΚ\T> orōîĄkĮ§R')MpЄOpQÜQž7FPČf÷ū˅Žār1˜í_ Æ~ëÜŋA‹ũÖū+wŠ'ŦķAł ~Kx0xė3ÜÆYŽn |Ņ5^”čjĀؘ×ŅÆÜåoâŠQÃĶŽ×6Ļcœ]ĢáS&Z*dųŽs˛¸'~ˇäÚ?ž¤Ųãtižåô,u'U <’<Ļū*N,ŗ¨Ōwāŋ"jâĀ5ąņU1õ› hÜo:•<%JX§T—´FŊ̆ŖGV™`.h‹÷ cÎĖV RŽ(ē•0Ķ l[ŊRÄá™P04 ņ{mėg ˇ4ÚAũ%FŽ|Q/qėQK1_”VVŖNĨĪ ęGŠ}úĐo֍BéqūËH_Ņé~ā_Ņé~ā[´)ƒč뇴8u…šMŖ°/ĐĶũÔE L™°‰T°ÍÁQËI‚iģ‡zŖaÚyônüP¯á Åņ Q˛~Ū­L! MēŋBĪŨŲz˙un1­ėؘĘEĸzŅŪˎŦ‹[yPn Õ=ž3gžČîGܜ4øxšZ%o@PŧŸíãķzėŸĪu5āŽWđĩ˛Tē[Æ y ĻĘ’-|\Ī0k!ȆĶŨ_H ÄJĖŅm’Ã?Ú¤!ĒJ…*ęT흁:ŦՏ`•ašą-Ũ?1Ōgî[Õ˛u,Æ´õ-×("û1Ē–Ž+Éö !Ø v ‚‰QÃcSûcĒ’Q ēÎWWˆéæ€Uĩqē0˛3ÛÉeįžÃ6M]Ēå_‰Wt!YŽĩŦ‰uš5P,ØŅv„9„[•E”m¸ˇ5ģS0@Hą'šIJ{&$"*glí'>n¤ų;4šY[Ãlmž… +((Ä ė.äģ”@+;‘|TƒëQČ FŠtG•ޞJ’S{PĖĘ7Ôë_XØ^ī!ēŠpCx…ktã¨Y‹­đNSˇ{ŠĪŌVvaŗ?°’,ëĢiâžÔL JˇĀ)+’•*QÛxĄ †vJ.›Ú‡bčÖg]FÉr™NNėVRn€37ŽĢ)C*čĘj+/šĀ)€ 8œĨhYøļÄsEÁÁ@˛¨ĐŦâĪæ8Ļu› Đ!Ēn˛˛OÉI‚F¨œąōYH‹*“ļWíZČV@jšÕˆ…caeA ++ėšĨ¤¨Û}‡d ĐĄfÛ „y"zÔ.ĩĶá°ö§ž¤)…(" ë’ށB3̜§–ËŠã°5SsTĶdī6ų­JĘ89_ŠŨ˜â¤ĸ]áĸĘŪwÚÕcëâœ"9ĸŸQÛJ{ÜTx‚KšVe™úĸx)Z+ė•*9)YŽŌ€Ø_É;faj˛€´Ų˜Кz‘Û("ËŖ*Žō°â …JŅJ š8ĮŅĮ-ÖFęĄ(Y<§{—KŌy{ uŦ…Ü.Ŗ{6€­û Ŗ”HU8A]LĒ•Nąb¯`}Čd0uíRv‚‡'(#}žõ4qãĩĕ”4ޤvī…Ņ’aËuÉCBē•*Û=‹(ķ“GVÂĨ]NĀÁŠŲ9,¤6ęę6NB ķ+ /‰(ĩ_aC–ŠYĒæë0@lęGd Ja‰“ ”IŲ~(ķā‹ĩQÅ|VW+)DV>u–qW,;y4ĩÔåeČŦ!UĒ, €)Ųt“ŗ,ibVÜcøė„Wy(¸&ĩ ™s]"Tx­đ§Ģgj0Š 2íSąŊ[nŦ¯°J<†Ëņ*û,­ĸĘ&ŸnBÜWåSoušB Ŧ‡d"JR› čΊÉWY‡Ɲ֛vÜė‰(GƒøhTrŅv9•…ķf *TĘ^dŖŅÕÔ{Q&ČZĘĩ6˜%ú&š‚î“@FmlŗÃHG#¤pž °ĨJēŒĒŨęUDŲH¸šCĖÂąÛ%HÕIjiA¨7€ļÂĨBļž ŲeĒëWSˇŊFÉ: UŒ,­ā¤ę„Ą°ĘėG7œSX…>ĨVdēēęYx(pŊĮŊdj!6Ú öŪ÷_Ž *†ŦFE8€#Í!y”pY™OuÂČŊŦČæę>7ēĒUĻĒ;ÂĖŪpáÍL­#dŽĨ3(ÃeTv…ĘŪ˛Ķג îûU˜{Ö\Ā‘âÆÂy,Å_gSTŽŨvNËxąĀm9´g#)%@ŪYAQ6D6dŠ.G5Ą2.2€Ŗ€Œ-ä÷Â„Ū´Üēč "ôZŠŽ4édâ4@(NļÃ,‡E”Ūčƒä•c}•3ōP_P6#"ËO čâOXč ÍaŪŽiŽN‰ÜPā‚Ë N‹:lSoe;ųŽžĘÅՈĒ]¸Ā;Q9ö.žYRo´Í Ž=ĘĘÛn§`ëR4;ci;l¯ŗ ŪT ėĻF‰Ķnk?5ģĸ‰âĨe‘Ô€‹ĒsåF`˛¸ovY4Ž•ŊčŽƒ]Í[”™ŧĒWHū/VÄy2VRĐØ˛Ė×ŨKt(HMq„:“„Ú.[:hUõ!_`íZ EÆÆŋ‡™Ģ‘AĻ@ÕIãâ0õ¨!YƒG~Č+EÔ2ĸö#ļTl;gcˆāŗĨÉOWMqÔn ã§Uqą)ध8Ŧ¨ĒAM„§E¤.ˆ"hYĩeÚ¯¨Y‘ÆUîw5™č7ŽČbîI :Įx,ÃEK+uũz†Peņu” TNķī܋ܲ7ˇ`äB,D#:Ö¤š6Ÿ]‹›•.<5:ėƒû“ÛÁ•n!ö,ÜŽÎõÔlT)­wōŖš§K†…5Ŧ¸äēQÅŠ¯ķEĘ'šuG‹ĸI´)qiRQö&ßɞϨÔŨ9hSsŽn+ĖĢ'1Scĩ‰Øû#n(5@Ņ öP,Í!Uڛ`ƒsZObĘŨ{Ķōų"ČÁ‘ 3Žë‡Z.70˛Ā„^n @ŨY* GĒĐ{6o JŨ2į ĘP]‚s˜ĖŽX?Cæ°ŪģäV, :/õį$ĻâÅ.‹1#,ĘÂÔkGKĶô`öƒø*xZ-€Ņsõ5_Á=ĨL?<ë§âǜŸ–ĸŌúnãØ°âÖd=Ö^ w …‡Øä\t Œ:ų_ą8×6jš™3uDüÕ*¯¤jgŠ“XŽ´ęUZĮ ņXĪąŦčQīÎāĘøœCiĩà —p[ĘU=ŽwÉVô›ņX?DüJÃzī‘XV |1 m0°Ū 4Iufį5üĖQŦÜÔčÔ{ã9ŦĒâ%´˜^@ę –0SčúIŨ™ˆ0°Ūà …îßš§Ū|–Ôŗā°x‡1ÄÎüų0 ŨxBŗŠŒ¸ZĨ”‡ ’Ģc\Ėũōf&đ¨âaĩi‡Áë •\:y1€4´‹6\&6;Āí§K%'‡4ųŪOjđ~Ķ’ZĪi„ÚTÚƈh2“i}ĻM|­oîTņÁƒĨkà šĩaũS~ Á^Ŧ˙Ģg…1… Õú[éƒČ%RĒúFĻz™5ˆëNĨUĄėp‚á,3|šmpofd÷w÷lf:ĩ&Hz;&44˙CÚ]Ôˇ­ÔĄą ĘČšĸÚ&īĖņVé?unEԀ ÍĪT#ŊhĪ‚Õ‹æfĐųŦ7ŽųŠõ_ę ˇĢ?OĶwÅ`>ŨOឧ„J15•ĪĖn-ø*ہXœ,ŨÎ;˙đŧ [“ŪîĪÉbę[EÄ{i˙˜ō=Ęŋƒ1nœįË Đ!>)™™¯X)Õ°^úM!æô,Î=×U+Övj¤âãˆŦGŠų…SŅ(úį|•oIŋƒôOÄŦ7Žųƒūx*^€ø/ũŸ˙“g…ũ3÷ŠĮũš§Ũ+ ûxŦ/­ų,0ķ¤rÂz–|‚;Īg†~Üõ‹ũžėėø/zcī •˙Ÿųk á6z'q›_ P9ŧGžEWÄx'ô*õīSp9¯=ú/ĄxZŽf™°ÆÃģ ę›đ^ õgũ[<#€Äk ëžEbŊOĖ*ŪŦü?M߁'˙]Oāvb°øē}%<€Ä‘æ7’¨á‚¸i#ō¯üWDMĢĶ-īÕP¨u¤üÃ÷HųŦYúĀ7ÚS |§f*ĩlMZí{jeÜp娍:•lF.“Ēe,Žā`G8hV5Œ:<ŪĐԊÄzŸ˜U=ŽwÉVô›ņX?DüJÃzī‘X3Ô> — > Áŋg˙äŲáaúĮīû5OēVöūņXQ˙ē~ ”ũađXORĪ‚đGaųėđČ˙÷ŸņXŋØûá`~ÎĪ‚đHũa÷†Ęåî ŋ낏‘IÄet¸\v>“úžßöŦfĢ~ųs8Iüg¸^›šæûcæ°ūŠŋā¯VÕŗÂ5ą+ĩĖÅŊģ„~ ƒŠVÄbé:ĻRĘîp0ƒ†…xQŒ:0īnRąŠßOĶwÅ` ˙Öŗįŗ‡ÅĶé)ä$1ŧ•G ÃI•â›ÕQʗÚŨr¤]āė!9ü–Ŧ]:ŲN˜Ŗf°@j*ŗ ßt@VZ[â hēI>‰R‹ũ˛Ŧûv§Cĩ1%æ'¯`YK¯ÁHWNZĀVl§v‚°~‡ÍaŊwČŦWŠ˙PUŊYø*~›ž*ÚGŨrc+Ve,Pæ¸Æn°áIĸ왤đUpxZ­Ģˆ¨2§Č{Ö†ÂV{TKÃ^ģė§KûĘÃܰŦp˙–%WÁcNZU ŸČϊ&9Ä;đE؜C)Ân{“ŧ#ˆü*ŌŲ>háđ 3ĶÄöa(ôM~1uÎĒz%\ī’­é7â°~‰ø•†õß"¨8_$8û•Ōą€Td܆5 3‚ÚWŪņ؈¯P‰ÕʑË2"•ZUéŊ°@3dÚYŠá¨SĖåF–~‹G΍y”ŪYūK ęYđ^ė?=ž§‰9h×Ä<}Ra:‘uŖ54€™ŧĸŸRĻ“ŨÖĐĨ˜j=šҍ”Ņ~*Š,!iÜÕΚ§:l đRWę…d!O^ī%e7Yauĸįˇ4RŽ‹įąrQĀ\M‘n§‚<ôYyŦœ5 ,ąe” 2€uMfFģ´/čô ~ P4(F ×`caĨ1„ʁkY\ßÁIäŽëlŌ1aÁ71§jwąv”ÜŖĩ iu§bˆ ö[ą°Ŗ’ĘE—F{Xƒ]æžĪĒéSĸČRŨuM< p ­>–MÕWF.OÄáņŦĻÖŋ,šŋÖĒckãØæS‰ËUÄë•:õŽņ]0a]0WâVdGZ#cžî%+vČ'H[ŨéÁŧQ“ž8#QËŖģĘČŅ~ ^¤2¸"8§ t(Ś2šrY8ĸP JąĪ$u­Ú?æSRŽy–puYŧâģļeo ŽÔã`äōã&n§/bu0Z)Īz‚f5DEļet•—‚✠Ķ*rےtĢiîSe&Č÷+ĸ‹œ4ŲyË>śëP!Bė )Õ4õ&<ĻĘk‘å+ŠĸąN"Ũ‡šWûAû­XŋØûáS.žąí_FÂĶéĢģĒafЊmÕuR>ęéEA‰r?1÷ĻāčTnųyŋ§Đ˙˙‚ž…žnz…šŊ‰˜WˇĨuSœ7¤˙<×Kôēyž§Lgá Ø m"Ę´î÷inÔęũ;q$ĩ• °ĻRĄC6 ÚÂ}‹¤ŠˆeÉÕ#îŖRF%Ŗ\ŽÍņēĄ„Ã8Q}Ge™âŋŦ(˙Œ˙ÁW`Ē2aÜAĒ\`‘ˉC ƒcĒÔæģDOU߂͊ĸ*PúÂát­b9,ÎŪ{ŧ–ĄQ aŠ'9ËüWHĘôë‘æļĄ?úęFVke8*=Žl=Ē~DõtĪüÃcŠĀvÔõO YĖsÁ'sK”Ę8wSk™S9ÎxAYF}ĘBž°ēĐ"ÁIS˜,TĪR#œ]5Ąš@S¸ŠD“deļæ„k*˜â€tQíR=¨(Ž2ĸɤ‹•Ø‚k}¨°1Ž˜[­Ęˇü•—P‡)E +)ĶTØ˛H”8 ēŠIļki™ö*˙h?uĢû|*dč>õŠĮŧMLŲåĪФÆŊŲ‡hš˙Ŧ%ÆĮK@ŧö˙#ej¯ĻĘy*åho%SMŒ}F‘—6—TqŪSö…„xķØŲũâ°ØP`8€™F“rą‚^Á>›CpÄd#jŇļr3;zˆWķec=1đUņŒh{Ёé$ÂÃâ\uZaÄāü+i°ŒSōŊĮ^¯ŽĪ br‰§X˛ŸT“?%_M­séÄis ‰#)ĢM¯#´, ZM mzy­7ŲX4F늤đ7ëīŧüƒ°ėĻ× U\Ž'€ˇâJŖC˜áŠÅāA9Xã—Ûø*lĒ3ͧx=CņØüEJmamRËrˇâ°UĀËQõ…{S(ŌnV0@ Â'ĶhnŒ„qíX°öÎFgoQ –#üõ p˜Ž*\%^ÎĢ”œ Ú4\įŪ¤:ܐ„dė(™ŌČÎĢD)<”6B<Ķ[Úë į‹ƒe¤Y4{Vé„yģaxŅ:Ÿ wœ-‹•u*Tė‹w­ņqđD+(›­ūb^î,wÁWûAû­XŋØûá7¤vVÇŧǘaibKŸ˜ËIų…ĐxaøZtķdôæö’ƒF‚Ë ö?›ļb}wČ*ž›~+öjt, >ōĄŒlK`zŽ›Sč˜QŽØ%b1nIˆÄ¨@†÷Žû;ūęĢ邯zcāąą÷ÂĀú–¯zÖũņŗÃl)ßMčz;ĨŒžôÖ3Âąĸ›xáąkûôo‹ėÄáŠbŸJ+eaË?ĖŦĒ “ņîÂ5Í3HÖËnÉî_ÖX?ņÚąUŠ=ĩãg4Č:'úØhāņ= ŗ‘ĻũáRúN UĒ ­i`‰ĩ>‰ĄHųÕá=‚V#įô˜ŒAš„opXīŗŋîĒ>“ž*‡ÚŨrĸŪ%ƒā˛ļĘæû5eœŦÅĸŪ*ũ¨šėë[öÍĸģĻ4A­FFŠčŨ,ŽSžĒŽel´@ |QĪgQG™ØVēĢĒ΍)ģāĢũ ũÖŦ_ė}đ›}â=ĨbĒFņĢÜĒ:ƒËį’9*^€X_ąüŨŗëžATôÛņXŗSûĄ`Ŋ÷–“ÄąÕŽĶŗÂ¨÷Ķ k ą˙fŠ÷JsXü3Ķ‹ũžÔĩxÖˇīžo‹•‹ũžĨ\īu0KHWÛ†ĄFŒžHĻĀŪ;S]͘šķŖÜ°_pük-íŲáFÔ{‹i†ĩƒX˙ŗTûĨQôņT>Đ>ë“NÁīŦÆîæm';⛇ÆáEΏ––Ÿ~Á—Š“rŗ Í1ˆšv+ņņ :lkö5Ŧ Éš{ÖPn…ĘWZ×]’§ÄŽ´9”dĢpYœđRŗ*ã‡Fī‚ŦĖF.…äÃęđ JŽ; Qį,5ĩA'x*mæ ûU|9Ũ{Ĩ¯á)ßHÅaq˙ģTžäĮœ~„´Ŋ3l°øŒÃô# ”ŋ¤f]ÅY`˙ĮjŽÜF.…˛ęđU)ĐÆáęŋ;wYTĒÁ1ūÂ5Íðk6FęÁ5zu†P&›ŗyˉĻ$ŌuĐŠôĘTOUvR=ĒĢ)WÂąÅŗR´†‡rÅcXĪaįaŪ›'u;ĩb†'B‰/ŌT •ŠĨG†¨ķ–Ú “ŧL~m ]Y …āŠ´ąT*2AÍ¨nđÕY`˙ĮjÆâųl5Z‡6C¨›ˆv3 ‘ÂėŦrûŠļ.‹ÃFë(œß†Įb2ŌĻ*ˇ.cf´kúËū;V4Ķ¨×ąķiŽ(ĖĩāNTĮŋvC,ÎöËOzwŅęˇ_Í ¸ī*Ž&ąüĨC™RđzfP'Ė;ø˛ŠËéÎn39ˆ¤A“ÜŠâ(1†ĶL|Ę>™J‰âĘŽĘGĩUe*øV8ļjVĐîBxŦká #œė;ĀŗdîŖÅÕm5ŌĮ;B čëã<UŸUõB¨ĘØĖ5#͏†ē ˛ÂÔÃÖ§Yĸ›DąŲ‡”U”)u(iRŗ§)ˆæ„x‘˛Į]PĒ6Ņ36u[ŖŊ[ĝ[/°B‚ކ]ŋZT ‰*ÚqRMF@Ąõ‹{Bč錭n‹ōŒ’-+Z‡ŊYõ}ĄkSÚŧēŪŅø đkôÖŗ¤$ķ*îŠíFžņËĻeúŧ”´9Ŋ…kS¸¨ÍSڃÛ/=ŧNRŧǧŊYÕz˚¤q2Ŧú‘Ú˛›6˛€í=E_3Ī"SZæÄi•AuOjÜO€¨Āå3PRŗ†f#‹—h]īZŸÕ(yní*LBÍŧĶÔPŊAŪŧĒ‘Úƒ,ĄŧAējûBƒŊM<ÅÃBå.rj´œA¯P*Ú)#Š<Ō{!—UŸš>,l—NęPņŒ­Ņ ŖļPcšx)+Ģgzoę‰Z\Ļ4QëŲ  yÛ3k!Øģė`äTę 7â@Yy—šŗ4¨m•÷–^)SãĮ5šÉíQÁ ŋ˙Ä'!1AQaq‘ĄąÁđŅáņ ˙Ú?!ˍe%.#úR9ß ´o_ŗ1¨W+—fYa#iW2°3)Lĸ\KMFšžŲBÔÆa”šD–0ˆ0LIuéNũRķ%[č0•pTm#Ŋzˆ{†ÄŌ6šÆ‰VĻdwࠜ„˛(Ž˙@z šø”&‘CâcŌ ĨÁCjŅ4R*ĘĄôņ•ÆØ*e¨ā,ô/ee^QÄOĮųOËWö†QģĸYĮŅĪųŦŋb¸Ĩ7QH)Do ËôoĐĩ0ĮH‹ĻæDQ/™Ĩ•.pC1G˜U8„åč\šVÔĀôv”‡ĄTRîR6ÔĢaã;abzlŧKˇ-Ą(ę!„YOūäŊŸVá EŊ8á^%×íQØŲ<öōLW1Ë0õ˜]BÔ>QyôbnÄ/åôåGĮõ‰4ņ ^Z›Ņz ›GJ‚ãQæĨâ R Ė]Dw+\ EÄuŗoX„(Wô™7qÜSbĸˆÚ&0 ŨTÎĐMøĖÄ)†iáSx4kõéۏQÃ.ĜÂ%ē ÆfŅ+qĮ׎âgS™~Š3øFžƒ~{§X9šSâ$åŋ¨„ÚĮÂ1 ¤(æxĩSėŗËĄáh‡Ķp øLhũ?CūĪ3˙{CdØ"\'zåśöTׂSÉû^ظéßo_ÚŖqnyĩ…}b/Ŗ3¤äoAÔą•‘YVå:JNJ‰ŧÄĨD[”’ûEŠŠŨÍ%]Æ—ĒŒ+!Bž”ˆč‘’/—1f´Hm&Ĩˇˆ0¸Ci]´Â•ĩąGk/4`Ŧ›X+‰^ˆd@ZŌŨ1ŽĻ ažamĪ4m™ĨŪ†Ãr÷šÄ~…ņJôƒėË{ ŅzSoˇtåY-Ëgˇ”ÉyCÚgæ!L(^F“hnšË6ĮÜbŦpģŽĢĨ6VKß^ƒ’*÷ųK°Îi…EĘé„öîāčŧ qW•û(íįB~ZĖ1Ä͝våYf°W/ ëPå0Ĩfœ˛jx×ų…}â+Ôs€l]/påČE˜z 0ĸ2ĖRŖŠ\cņŦZļ…Ë„¤#3x…^‰v0ÄÍôĮĐXą‚‰ƒ0Ôh0„OĶD¸ö‹ˆžŸI‚j÷*zDŅĪx›e.æ ]\Šô0æ8ÜĢlŗĐ#6ķ <ÜÁ/n ôRô%Áî&k˜Y}$>ā‚Y93é%<ģ퀌%á…ę ÍMJ÷ žķāÄ€ÁQ’Î’/˜øDvīŪÜū`—Il›Ũer°ša/É^˰-xĒ æ˜)¸u0œ32 Ho)¤ôŠB=LІũ'”T)ƞ÷ ×FīqŦÉŲŲÛ¨ėxws{[Ínq pÎÉf-zaÉ#hÄ&ŌīMž`^Õr'4§YnY¸TŲŽHĩ#U‡Sč%ÕY…ĩž"á$w ÂEm,n#IÅ&Ä)`G„ ôgŒģ¨y1šK8 {>—r.=f\eSR e 1ǍҭĄV ų ¸MéÜe0šLcŠY*^E ‰Ŗ"ˆn é2 ’eÉí0EęG'ÅVPj%Ą—NĒPœ(‰ŠŠ{KD°\Oh™]ƒ ü1 éõ+ŅÁtÃOu.DęË š‡EXÖspv¯B™i$ū!*2ĀĶ27†ŪÄ^b%æ|GfWhčĀkÔ"M1đ;•Õ!Ė1¨ˆšąE5(˛ŗ/ĻXĸõp’i#MŲĸ`HĐÆÆ\ĀΈnZ +†s }€\Â(ęnZs[a¤ Z/åÂö—ĩˆ …–ĮR•ô8¸Æ´AŌö–ĩ0ŠĸųŠījQ„ģÄě¯@-—›Ū#jĨÆ—<ļąîī˜ŠxbĄ›–ŒyM&T`3nĨM§ z•ÎŒĢāÂĐ;™všžŒQ2.{e-ÂaŅÖec9°Ė“4Čb'ieØ"¨{ÎJđ—NX+ )‰LËą@âG6EOŠ–üƖ[všafęWNeƒ~I††Ë–*Ũti¯ŋGPŊū¸ë'ˆ°zę ÉyK˜8•¯Ŋ„Ē€×B§Ix„8îÁÜ´y—›Wé3Š×šA& ægæõ0ƒ¨ĸ&9g™–“Åa(ļ`ލT,zôxąÜrQLXžŅû€SYËp„S¸ YČÅŗ>ŅĀ[ŠUĮč…'&ĩŠĀÄĶØÛÁ í,­ÍŠ(ĸQˇĢDAæg!úEÉkT0Ģq^ B§r  Ā™•+ŅĨ<ÃWųĖB”;IšˆÅŖ2Ž“BŠŽ!b GŧšÛ/Ē]iIQq%"åÄ܇…§wŠiģŲ¸Ø~ŽXšĻ%°zÛJũ‘Įá—q†î9bc‡Ņ'Ŗ—1 ˜/(fÎæ´1{Môi-P9=LjR˜ę#ĩē4mĸî[¸ŗ¯€æ|МG[8†*j<ČnqF`cä5õ–wˆwHXŽu%ŠWqî)Đģ™čĩÍLąg€šÁ(od¨€Ą¸ųQōa´zG‰}ĻQÃY‰ą[ŽVY—'Â~ėMë¸ĸKîŒĻIn ­„“;ƒ¤eĢ—æPŠc,\Ãŗ˜Ą Áė–ŲĨī28:ŠéfYÉ•€ŒŒ…ĸ0\QĸeIbõ™†5pˇČŖ=Ō‘E ĩsiŗPÍBAs5Ä;fš‹Øߙ¸ËģÄoܘÅŨłđ'ZŊ˛Æ€Ÿ ĩQF‘Tą˜Zn^ĻYšL ČĐR8q´ĮP:–aôį‰cØhû!ˆ˜gPzĖüΠƒjļŲaw:„įĶ,BPŌ ŒâČģŲY›„wÄQl‘dŽ*Á1įM Ž]RŲá-b:œ9ˆd0a¸Ô=ę-艂 Ā ņŲV¸ANâtœĻÕfS6‡(ÁŸBbQæ]c,‰āųR‰{lÃqa„L|LšGâ2æus, YPkFe(nd#u ÍĻ*d×DK%ž`ŗÛ@@nŅŠö‚Ty"‘]™ôņôP Záĩ“žĻc´"ZČä ÂĘĶF †îã•ī‚S[œn 2ƒwu9ˁ%֜Ņģ`ųČ,ŌyÄ2Åy˜ ī‰L¤öŒÚŌpâY ?B.Äįsë፞1Lx%TG^ĨtúT%–…* mI2bbą…UáĻę)ÁPîĻÁÜ"´‰ĖEˇžã•Ē…Ū%ˆzc8@kDĨeKWˆK_?Q‚yĖ.<ĮSœ6 gF*a*‰z­Ųņæ*äÜ@ Šz!ŽÂØiŽ,5Ā?1‘3å‚ŪAūYe|ÄŊlŽãÅEf–f W¤Č‚Pá-ŒŽ@›Ŗ3锄VœŽ|%íX—šØÍËg&SamnžYq`ØĖuŠ/193nŽ•+›]Ē2´#Œ´ Â×SĖĖLŒBŠë6“āCŪ&å,žlĩ03™‹•šˆ‚ŸĨ˜õaô%HÎBÉi Asšš`TeÎĐ&JŠ–žŅŧ7Üâ×wĨŌEK h1R‚cl)Y}{GHĖ5{ÄÕ čÛŒĘ¸Á(Ōí•Üt•štė˜ÖЛĄĻ=E“„bÖ# *„Ļžëi™í”iÄ­m•œÂüL å ŲŠn¯˜é›™ LH֑Úm]~ĸĩ… 1D/h@QS<ņJÂ^!Qh-̘Á{Ė1čXĐÜđS­L‰XI`ģ™…ÄTtƒœæ6[ņ(§š˜9…4ŊŽĨ˜Š&i3"%}ŽŠ9“šXÆq¸Šo°•㈄YšaŧĩW/y‡/ ~阉ĻvARŋ#įāpĨ¨7<Ē*Ō@Â-ė/OG6ųÖhŠ‘ÄY,74'”Xŗ¤œ5´*Čŗ&Öá yM¸šÆˇ„ŽbUû0‚§2õ4‹|Dk.Ņ4 ˇÁ*,ā+h¤BDĸ™feS>á*€ n‰q¸ŗD÷3< á,Kô‹’L›JŊĨļn^ŨÁJ8–āŽX€*ÍĮ/æ#Ũ1ûc-<ÜJHŦ…l#›ŧĖeæĨˆ¸ĩITģ@ÔtđCĻȗRæôA@ËõœpT&N{ˆËŧyŽ ‰Ę4ņ/ss„NĐaŠD%ÕËC§¯™qQ[RāÔlAOv_1+\ĸö`w*Æ|uy{c§Á0ú Îīî—åpÚvÍY5ĖæÉB–ōŒâĢÄۙĪZ–UˇHVß3Í>ØŦæXL@<žŒ ÍÜ´ZcĮrĄ2MË,ZáDs¤Ī‹CpО"Â0Cq˜šÔÎĪzEYá0Ŗw–Qa=ĩ¯30“Ī•æætm”X8ĖŦ¤gå0=Ęüj5Ō)8Œ÷“)é ŒÚ0!w05JčŌÆģ d¸ú”ÃÕ×ēÄEP ļ…ļŨČÜ4ŨÆ$VK‡ƒėš`ÖãMJIŠş¸V„ 5ŽĨ…€uæŲÅׄÃy{ÁŒ6tjüʋ\ÄÔDv(Ö%6ÉųĸjŲ}•yÄÔ@mIĻsŠZ›% ķDZĨŸGSЧÕ¨Ãl+ŠX÷|K æĄH °ˇ\Y°ķš!HĨÜŒĮŠ—2Ų40gb8šuÂ)]LŠ’Ib@ĮíYĖRüĖܐ”vf&6Fz:ôÂĢ—ŽbŅÂydb‰ŖY4áFļ6W˜¨ Æ qi/Đfē–čēœEŸåLS$î&g!j^ōˆĘ-("Ās-ĪęÎbVŠé…į ”™ļfŨ]ÁOÃÄWŦúšf,PG#ÄŦFˇˆ+€E<Į^¯RČąō‰.3))g…Fž. Q•štŽ!1/de‘/.Ō´{Á´ÅĨJ¨ļŊĖ„ä„W`/(fŅąOHmÖr´%ŋ¸•Y7ž!Ÿš`4LZ_„p‹%W2ÁŋF\L=ĻŧîŖŲ?q&Q2˛`hšŽÛĢS#Ŧ’°ČTVæ$ĩq(ˇˇ9” _ŧ} Ë,˸u`*eȆb ?ŅM¤2„ā1*Ēæ}H@ ņ–1ëЂŗˆ‹ ą ŽŦ=Ø"ą|ĘWD.eö˜´ã]Y‡W(\y›ëI|Ąŧ Nõ5jæPŖfØ´8f=™ƒĘ ũPPœéKRĖ4H/•G<øTøfĸĄ/Ŧ°æf–aXáЄĖĒ”c.k UptĮSÎÁfûÅĀi†gĘū OĨŧĮc˛0h2@€F–S*ę¸`kqîL´ŨëŠfk'—‰yĄč×´QpFäÍrÍuĨ[I˛-UfŨĘŦ–8¸Šå‰h ×PF#47 0ĩ(ž ‚qÔEđ‡đ‹†âį`[sāĘ*25/ÁxŠÎ kŠ4'Üë$ü+(øôSp'ISˆM†[ėžyM6m¨7K›Q .šR×’Ūa„ÖÉ`–æ`& &rđMąÄŖ^Ĩč‹{ŗ ĨVü%— ČjĢX•ôĩ„ŌÁī˜AL\ķˆ%Ã€ā’”ĘŖ\%U­LíˆŖĩ~ \iS/˛ĨÅKŨw+dØĶķ ėŗæaĮĄË}ęĒY•)Š„™ûK‹ÜĻfYAøcå[ÉS=2&Î"šxˆĢ2eŊûĮtv@Ģ C’{fę}…p ņ j Œ„E3R°=Brā˜77ÂÆlCY…Ąo"+LāĘ ›0˛üÍc‚<ēšq7჆dƒ˛u.ĄÄĄ9–F⯨(2eš(K†*Š)m":njĸ{ذ´& x¸*­CmMÅÕæ2Y™ŗ°Š•÷˜öĨÆBwRŦãVb\¤;&Ŗ(crâ(Ų㠗•ĐŸ´ŠEúŽāĢæRĄ‚ ŨĪöœ\Ŗ:­đ„VWˆ!æ@ę60Z žž¯0!„Åū&$0 _aY”G.˜ĸZŸĸ!˛û„5˛^!Jåa†Ā]Ã%oæ‹ čaŲc0Ė29†ĸŌž•¸ŧöÆÔģ:ŽTŒ.Ęũa<Ļ ž#”J,Ü`ÜĖšƒÚ˛!Чí(RŌ]AnEËõtå—Kr⨨X%+šˆčĄwéÎą ļ&ĩHtD#ŨVÂTGĩņųÛƒĄ™p_ŧtF"kuz°K¯ÄARÂnŸY&Ž1Å™ČI•ö‚{j ŧËúf!ŽÅN“2—§”Í(Ú§$\đ- ĶÂRŦ øK`Ŗú€ ˙&Q7…õ—w!˜—.-14Tčy:”¤™ëˇ—. ­,J3eSÎØÍCk\ÉĒgÉ.F%„v1]ĀXŲ-ŗ-zejũÛRˆŧBĩ˛2đ‡{F;č-ēęvpÁ„`„Įã…Aœ„Ã6ˇø„Á,HBŦÄzãˆė÷@ŧc˜¸OÂŗ1#Rāc3™nX-×*ĖX]ĨÂË$zZ8 šŅs-¸Ŧ ›bĐ÷–3´ŠĶŗŅ2˜äæĒcÁo†ÙjîĨ Į™G&j9L#ꍂæ|K,0MeĪš_¸e(ník?y­Š :Ü:ĸĩ Ą™1Ķņ˛ŪâVoÄ ¤ĸ1¤X8Æ b.åÛ-›‡ÕĻMu-Øė=C&:5&˜drŠ‘ˆz™`҆`ĢteuĨŽüĖ€ˆ6ÄĘU<~Č,įÛqņû)ĐnåJMKqī2`ŌX}˜ÛrڃJĒąž¸TJÚ>“/[.ˆŽÂ<TĖČļI–ô}§™eĪ˞ā€qqZžņÔL \*.a´X‰Ļá•ΤÁ)8j •"ŋŗ<Ė{"XK¤ö„­ÍßDt§:$6ÉU—ŅĮų‡sc°|EėɅg­v‰Ü%O6‡ ¨ģ,˜ Xŧ=eŠô鸅j_á3]Ț÷%à Ļ(k_Ŗ e ę Rs$xhŠ9—ü Āĸd åĘ ļÁ3c7Sļ„YM5* ũŗ dJ̎ŧÉ53PhķŪX ú™\9qĄôœîdw •QæœMVg.åŸIrĻ<2ÔōäˆōũÆ_ŧLN1ˆ†Š'Œ$YË^Đ,eŗ›a•XæŲaTf ö—Ķ6"ˇŧ{Kmc\ÜLšŸ&)ĮÚ%L.`Ā /)™ 劉‚æ1ƒį/VŠ—6jČiW‰cQИ(eāØ%@”î=ŲyzKXÔ6_ģĐ8nŽYŋŠĘŧFi ?rĒ­§#‹Hg]ä?ŠCB-­Vb6 aŪQ­›ÜėĒ’čdŠī¯HÔĨÜĩ,ME—/Ë|ûJ”˛ļ‚ģ"˜Ÿø;§ yÔA0/ë(ŧJqŒ"[ •f°0%`ø•oo$įÂÜ#5JÜ)ÚŪÁâ.ŽŲߙŒŸ:— “>Ãâ;ĸ >x”‹°ún ߘĩ´ĸÔå9Éî11|?‰kŦEƒ­ĀŊŖl–ŪÄ˞?"Z{•&5YĖÎڃrĸøošZę\¤W‰P7™æâ Z]įŖpħjņģe 3tKĩQd Ø_3m]‰€â^u1m”×2âÁg;‡E¤´âˆøÃsÂ,ÉrčÄjۜ†×r RĮÂæÖŊĻfÃWâU´ÁeėÜQĮīŽ@áŧäNūų xJUlũ°œr‰ĸ=ÂVŖp72´† ; %ōĖkĘ<§íĖö°%K‡{¸[Uuí ;ŧĖCj´aJÛŪfåŗ— ŒD^åV#j„­T(:€AVˇÚ]ž >‡gd0ũ˜ÂëQ™M`– ēʞŖkS{„˜ÉB` Ļņ&‘ ¸w?2ė,§]M¤,É îĄ5ÅĀ4nv‹ˆŪ.<°ÔU ø•ÜģžÁ5BŖįVĶå˜IŽP"턹ÄŌ77ŦNĘšĢË–§2Ž…¸ew Ņx‰!QÕ*šá o,Y5FŨ—.U2Y,Nĸ†ĩÅŽ×KÜ%åíEO7PáŽb-sF—  Ė[‚aeƒz€Æ ƒz/2äŒCěcK'‰Câ;†ĐÎϞ5GIÂŽ‘ã.° 8ŌÁRÂã’Ôũåp‘iK8˜Šîã§ŧۈú׸ĄPnZˆ)ĪyW`ūá”ŅŨTëoˆ|a¸ĀO*ę:3{cˆ[ûËp|J]L+ĖŋķšÛᖮČķĖŽUfQ_xYÁ[d­ˇ(.)Ķ#‚^ņâ įˆÍ) mFpqYš˜°,ŧB[ye÷—1æ(;=!<Ę!nŽ+ėLÆ=F7,ī„gėC÷HũuŦžT67‚3 đ”Č—d čEJæũˆĮi‚j­¸J‚ģ„ÅHFá_ËŦļŧÄ"öļ15'hbße5SBC̆Jā&ƒQŋ€'˜&qĮ˛3ČPĢ—83Ō{ŽĢą0ęÕ#DŅúF Úąf C”ÃG¸Ŋ’‡Ņ%û{ĸJũĨ´6ø‚ë~#Dąˆ§œaWšWÚƒđP ÜŸr n™QR\ãLĸōž)Ę_ņ9ŽbÚoVĩ$Đ Ô-E]Ø9@. ķkuE!oÆVoŸhŠļ`ts/ŅOxQ"Y6_yxYļÔCT¯—2É9Øė”kuڂgŽˆŧÔaŪ`@Ĩ m•Ū/RŌ„cjŲ{Œō5öŖ¯JĐBĸķ.L@č"7-ÛĖärÔ cÔšÛ‡<Ån§eæ&9/*ĀEÂŲŦˉRPn]–‚îc1Äü÷9ö”J`ē!‚zcWoP+CĪsJ.îV’š‚á#ĩCobi%T +2C~%*ÁšîĀ-ū`ēĸĨĢö#˙y<ÛKķĒŖķnĢâ^‡€‡yCę% gdFR­Š ģVŅ끃$§!‹,$âÛy”p•1`K­ēĖNXĸé"JÂ÷RÁ `8ĒŌ!÷ôļ‚Zę-h–‚Ã$XZŧ(ĻÕЕ¨0Â{Æ!\á)SFQtfēwȕˆKä‡1hŲšAbŽSwÉĀ`ļK–,_˜¤nĒ!Ž…ņfœŸl1ÁLĻīXŒ#FÁË2ŦJæeļ4žjZØūžcîq?ÄiˇpPAFI@…Pí/, +¨†( ~Ņ”ĮŌC.Ņ2…Š]Æm‡Ø‹qœÔ-ƒ˜Ë´C_Ų‰@čÅĐ3¨Ģ^eŽŌ•""sĶö‰K`á…Pž]ëī3Üë3ģ…Į^ÆCP¨íø‚4v\¤ß¸;iŲ”Üâ2—‘QŌl‹å˜ A06ŠĢ&ņvSŦ7NWDĀ‹2QĀ•šŗ­Ģr'Y*ôWĻF[zuëŨLa5rÍâ_Ōh]+ ͜xˑRŨF"Ķ>4Š#ŲŠ3&Ô`ĻQEųˆ¨mÕ/€˛âr–*`*O1ÔRųâ3&Ą@9—65/‰b]›„f3cŦĄm@ķˆŽÁ˜î%hf+‚)ŧßP ŪbÖYa TN$Ž[ø#IIŊ\¤xŧĘx;đ<IJĐší)¨ĩâ;ą‰Í&[ ũ?‰{)öbI˛5øĐácT6@8„ŠÄäãŊ\Ķâ;îé3r5æ…@+™U‚¨.eËG (jŠ ˆÚĢLŗypNøNĨw¯hZÍÄRë\ļ9ßR´m6;\Znú•T.p `Ë„ŗÔ &{NjJøĻ†>Ž#BÕaâ%ĻÛ}‘ĸ)ëĄ/cĘ”-}s?8Оéæ$ŧę5ąc•™CpÎģƒgŌ#+ļ…ęT`Á;UGqH⏊ą-‘ŖRéLëŲĀâ]ĩ°IRî@åüķ2Ŧzx‡ 'mÆ )ûeŪŌŨũFæŽ qvđWÚĨîܰí0ú˜žšš† ˇGS…đB6háf=žĶ,šCķĖ Ŧ˛2Ja3š`FãSÄĸÜËĢ5ŅlüļB×q››XŦ%=ârŲ,Ô@îēc €ßîĖ†G!*„ĢrP.9ĩƒČSŠįˆ`ęāüÂc1t‰˜Ą"'ķJžæ °ŽŖ˜/‹a^&!ÅÅaĸßK=Ą§2įÂWŽ\Ę jħģė‰ŅˆøÉŅ2÷놔î ŗyXJw-Õ3čˆĸŖ„;ŋr…Ļ;!VŽæōĢâYâĖn 9}ĨŒĸĶáqYFÎXļu2đîp/PûŨL…zv5(ÎZ‘Ĩ‘Ąí–äW&!Āuäƒ\×ŗ((ÜÉ,ôuš‹r$Ûj2ŗņ›˜âB(ÚZ„kÉlĢņ„<Ų¸ŧÛÉ EÖ GŠČ,Ah_ŧŗ.Ž ŋ1ĄiĶ”ļJKÚĄ‰$­›ˇ‰•ĶÁBIęST‡Ī)ĒLÚ6÷Hĸ5ytû@Z4O e7YÕfå5GeË)Ŝ۸‡RŊĮp!'Ō2ŸB=ž3˜—pæĢ ]GŽÖ˜U“ŠČm)—Ų°L­/ÍmœŽ`YEîęd6k%›ˇ¨ƒá+ ÉD*į ŠÄ^DŌ¨J€†NЇRŠ˙\0ŗÁW˜[‰ŊÂiQI2G&U!gt@ē^íDâåkú3œļsšP֗ē4jŅudĩŅŧ%ßŅTĀnYuUŊ’˙ĻP8ę0ÛÔđįķ2!–ŨŦÂ,Ļq°m đ@ ā7á†Ú)SŨDŽÍĨĖCÆÚÆáõLø˜sĻÉ æÂi㙋ÖĢ$Ūd+ĸŗ{Åpn„Ž™SÕK#÷X!Ē$áG"ĀARãqRŠģŽ&œĄ;ļšĨâ?ĶĜsŊæ/4p 2Øyŗ„g›G˛RŅĢPĢôL ęaËÂø˜ô4×2̈Æų¨˙éaÔ.q€¯˜ũZUɸ~#Û[$žŪđÍĖâžę1ۃb¨Ē]kG0É-„ŊĨ‚ë䚭’‚_2ąōAŪûä­ze™eĒØŽˆi/™Uéu…ēąoL$ĖÎŊÅîHÔ:´Ųæģ‘gÃAWÄķ  JL$Ą*ÍÉFn­ œ\ēš,lMƒuāš™Ũ—âŽLĘ Íž-øˆÕIx#­&nZŅYŒŗ¨Dƒ˜;––M_(J3'+ĨAÔeŠM‘‹-MøœŌ”f11eÜ{•ci•î3pņāŠVšÂ0´8‰qJ˜dËĢ °Á Æãžā¨eG0Ķvj\LÖ ĖKŦ§r“ŧŋ‘:>¤ŌĢ‚oĘ*-—uuyQŖ%ÄĐ⌠DČ9a7ū‰Q•Ü0`UĒ„Ų.š* †"äÁ Ļ [ŒwßŊŧ@ĐËĖK}ĄaŲ†‡2í{&C˜trmÜ,~Øf(ŧå(ҰŸhtoí,—ā eŽŽ]Ãä™m[ŸlËXĪ%÷.2œØkīÖöûKÛœ´]D‘CGų"‡­Ēü=ÁčsxM+ÕĻÃĸŠ~ō*Ū:˜t|o0Rļ2âã8öyŒžĶŽ„åĖW¨,ĩâ-1ŋ˛ UÛ;Ræļ5ÜĒ•žæ Ĩ s ûâ[ƒŧ‘‡‰āįSˇSķ ¤°úbĒŧûĻkˇ7-ĨĸĀ dÄdYš6¨pĘ×1âán ‹aÛ΁”΋3;S´Ō(ķ `zP¸ bˆŠĮ´ÂĪũD;\ˆPģXŖíĸ7Ę"ũ™7ĨJĖRŊ]‘j{Ú<å3pŽ‹WV:ŲĐ [#Ąx‰št–  ÚfIЁžxæčOy˜aĸā@ˆWJ&#ũ!‡ˇG2˜å •͏$ ÙebÔ3T'>…D–î ģõņ†ĒË%čē;ņ´šküōQzūRėČÐâZ™ĢŦú`1”Ãũ}įÎĮYåw[â LĮˆų˜–Ŋ"“hĖ8XpŨŊJÜĢ^eP§˛—™aœ€tĮx‹÷ŋâPĮāJr´ x‘ČKŅlã“(Cpn4k–B×a´āˇį̝Áõ”yŒOuķJƒ78Ūá;'’ŧĻ1ōˇ|ėŠ L%eSžĻ}î¨|Œ5”JúCK#Ļ“úL‹đŠė'6ÕlüERŌģŽå(ņfÛĄQNōƒRH;ˆ2´^ĶŌŨe)7/n) ŽŠ‡†ņrÆTp;•øŅŽ%1ĘCZļqú@†[Ė?1ŧFĐCUæV2˛ŨčK"ō•\ĮvËPÆjņMŦUŅ).‘ƒÉŽÛķ(9sšfE_39ęWŧö[VčF[vXä•Yn'Âa‘“æc?õ%—ŧÔŠØD[ÂoK/0Z”á*Pōī¨e\ŲP„)¨tĀ2&EûG8qæĨŅg ĐäęlæÅGŗ™ĘQjø3†ŽÂåŸÅí áĄHü1•éöŦĮuû §mŠn6ņÜlû ÉԂĶᄹÂ5ŋ0é–ŖØ=‰\ûŸ÷‡.-ƒĀcĸ†€Pô, ÷Åũ×1AWGŌ@ŗ”Č6iŨKšžŋŒ@ÜÆP|€6wdžÆˆū÷Qü~ž¤Ļ ¯0ä×XŅû§Ú*âÚáwz%¤3ËhØNk#ąYߌhˆi>Ķ”ŽMŗ˜đ5ļŅ,rÆ#ĸoæ[o\vÚĪx (ĮÎl‘´öW€˜‚yU1Ž.˛.ˇ‡zŸÎ~Ļ{ŅĒŌËŦ× GÆ+4GL>IÜiÜāú¨áÍʁƒ×‡\Ë-)Lō=ÃdFŸúÁhŠ;Ʉ­ĩį#Å á\U%ĢJ (—›CVŽ ÁzΘŖ ēƒŲ|ÎÆ†Ŗ]ų÷" -^ü|ĸ ´2ŠŋŖsą(ŽžQüÅb`pXÛâˌˇŅܯÜUÕT‡Ė5ÍKšžŋŒFˇHĘæÖ“q cs |œĐŌÂö‹4Ī8ÔÔėIE›ß{hᚄ70`V×Ūã-ę5qÄGņ™Æ ܡqī?ŸËŌbA„ {HvyÖeŠšÜn*ifą>ĸ!Ę3Čŧ°Æëļ•ņđ”ŒˇØ×âŖąw˙(M3'Ë_ÚĄé…ŗ­ŋĢ'ęJĘŲB¯ TčbUŋdĻ žÁ,Xš: ģÕWĶz!™é Wšüާ÷ēãõõ%602ū$ˆ‡ĮļX<čŽô„ĢĘčõŋˆę9~Ŗ^ĒĪ!6ø‡IcNoäģöŒš~éüŽąéWs^ŒV~ŅÂ<Æ~iĩđxøHaŸv7bŽ"ĘûÄ(¸lt?/Ĩ„Kv×\¸ŒŨ˜\Ņ´,f.ÔeåŊ0öm Đäe5õgņúzĄq!ÁļŊ˙)SĄ‰Vũ’˜6{ė€.šûBø1y+3#œķĘŨ¨zÍ6>ĸ!Ę3Čŧ°Æëļ•ņđ”ŒˇØ×â3*ÂĶŨ_¤ÅŦ<ŨĢôL =‚5ˆieWzÈéŊž PŠĩÎa7q–8‡F`xDCô7eīÉ­T „dUžō´ßRņ âŖ l÷ ŠģKˎTāî $ø1l–ŧF(ŨŖÆOPĩsŸŸĪåé7Ũáü^۟+¯OŅfWÁÄūŸPRÁƒŅ¯ī˜ÂĪ”oŊĩAí‡Ú‡û,ƒ9Ē6û îYhv‡¸ˆsíøž“ūQmđ'Úz}üާ÷ēãõõ%0Ģ^ĮĶīž~?øÁ÷‘ũĮķ•Žßķų]}tūŸoĨ?Ũëë‹ņŋÖŦŧčGošŊÃ#Œ ^P6ËŽõÜ`rū?OT1ņ˛v dyĸ5Úâ!ĖhˇâúM ū‹z‰ü~۟o¯GŅfWÁÄūŸSøŊú¤qRĸ‚`bĩę}¤Ņz¯ä4—x+ëŋ4x+RADÎnŅÄ é—é^Ÿä‰åēƒKxësŋœļ_XĄKĶ78Jģi—§WÚ`JXÖĄđŽŖm‰üū^“}Ō_Åíéđ6 GҏĪÑŠīÁh^~Ŧ2žhÔfÅøOÅĖ~­~%Mύ%Úî_å.8‚k›!鐉ģ rĮŨgÚz}üާ÷ēãõõ%7ˆQ^Ÿžųøô#ŧˇĨƒī#GiĶ›Oß?•××DwČô§ûŊb;ÃzXΚŲKāß3Ĩ}āNsŊĖ˜ĀÎ: S‡ųP.•Ĩ?‚ŸĮéękėĢžTŋË:\q×6CŌ !Į›Ÿu™‘ü/O‰ÕŋOEĀ~~ˆ•OxĐԐŖ|fŽbĒ­Ë^Ō‡ęs ķsí!°Éģuw-ÁS4Đ9XąAûĨ‰Eؘ™f§Äl‘íU˘žœfZ0ē8`‚fȑ¯Q”‘t2Ļ|bôÎUW?ļ–&X/‹emÃ'ķųzM÷ˆˇ§ÉÚ+.<ģö™ĻCŗøđf?Q­ÂbĪá3 ĨŠ}cԈŲ`ŋš–mÕøĖEÆ4XZ}‘ß‚įråđÚ÷‡pūŗ–ž0 ũČC- ԏ‘ÄūWSûŨGņúú’˜āWÂ4˙'\kŦVēIJböÎ ŲķčëōŅZūDÂė°‰ķ(ĮJŌís—īĩČĻUø€—iã¯Ē+¯Žœ´2ŗã,“­ĸq•═ Jēą{6ōå–öYđN¤A–áÅ×ļ€Ņŗ~°ÁåW¤ī­"|ëlpķŲu}Ø}"hâ*ëĻŽQ™BE;5W—÷čŅ›…ģÃú ~w._ ¯xqˇá1¯fÆØė á~$ ERÅŽŽŪŲŋyģģ¨uCß,.Jˏ.ũĻiėū<ˆŠ8a´û"áZN+ûŗâUļ2eM‹é Õpy€XxūËę*ĐËC ‹„ŽÃ—íáoŠU{ë%e9 4Ž 9‰ ŗŠL8ZųE/&rU/iˆD=›J-Âĩ/{fcË*•VĘ[Í'‰Hމ˙`^BšæQ…Á ÚnœK4˙Ŧ|*ā{—2ŊKāŠuxT^$¯×zāË^§/qúˆ'į;ŽĀį<œB2ĄĀąōDΗų& ¯žáø3‘)ÄqˆƒŠo(éˇîV¯ģ…üąΌãß­/æØ@zęT ac>—!CÂĢGņÔb‘ĪühĸāvSrmßēâ^¯2NÕü2Š/‚~e;ÆMM%/ą+U?–Hdō×ũsøT‡Ū8—á˙”yČ /æQPÅŠø!ĪˆÃ ÂŠÁę¸S&PŽ’æÆąŸIZņ8†ë9x}’û{ߊ›ã¤gä SÚbĄ(ŗR´ŠŽ•GŪâ4šp>#mEŲ‡âRîŅ7|?b §ĶÄĻ ū÷0ũnö&P•˛¤:%ĻGŦŋq1¸‡šo…&đže¸ĩˇzĖĖoAĀÁ+WIrXiŖA=‹ˆ˜F°§Ŧ@bš‰~Ņ’Ėû… Lņ•_„ŦJhFAaá>MŽå{oØ`ĸÖw–K€°.3ZÕ%bā6ĘÕĐ-ã:ē!‡ ĩÍÃöäĩMT¸Ũ ĻĶÚmq‚ÎÁđ`C"ĘPąFš Ô!h.Œ4é˛Í”Ę/‚bŖ*F7}ņĒ]M œ\ĻĒģÜ9d° üOŦðjܜx™â—„mĩ[VÄæõv$RVÖˇžØ5$˜ę; ÉŠpYK %Q‰Ō?HÛąf`ZJ%–P_Á-Ŗ÷ÖÃ?^ZŗQÔĒsâyjŸˆÁY¸;]D-†0;NŸ”ÄJ¸Äĩļ/g…öÔ"ጠ¨ŠLųDÔ!MĘŗĘųæ2ûĀŨAĩŽ Ž|ĀĻŧJ}ŠU,p¸|„H Öėũzn€Ķâ{00Đ:ŨKÁģdDŽ+VYp\͘Ŗ4ÆíD zĮŧĄ6‹˙f ÉĖÜ_œÅÆ –˛čp&#TXiĖ R6Ķ_¨h „&͍Đ%Ā™WÁŪØƒ|q Ÿ¨ÉL|͜æ„9 Åęß,\āZLíƒ1Ą`ņ.ęėÄēQŠÜęĩ;5P¸4X ,*ŧŠė[*Ō)jū’SW|Ėk–"Íėƒ]ŧĮĀx–Yx–=0á!´˜c3끕ŽÚ~ĄZž*(Ū.ö@&\ū ßÃrîJ9• +ņ ՛™hå+mârF‘5ŖË™h†Ũú B,â CŠãŠp.m—;—EÉõšáž÷-TÅŌ`ËP ERΑR‚šĢ…¨˜ģ˛Æí¸j€ËŪ'_ Ŗ× Ęąė5)*¤æ XöÖl ͆(čĘâSaÄF#‚ęY´¨Šlp •cōõÁHŒÎXj‹AËõV‡ĀHŧîa*ö"üKũĨ‰TŨ ŋ–DAsŌáuīž¨ū-Øsö/ú@$ŦÉk8Ōd ß/xZ–~ķp^pĪõ›F:éƒļãqļ7g:Ŗ5@N¨9n^",ÂßÃs*ÂEĨÖŊˇĉėņĘęPŋXˆÃQô$ØGÚ_@}’#$,öd&ka­IvŪ ¸:†œ#•ÁîĪ0×ęé ŪĀŊ~îΘ'+ä%\úƒË6æYⓎ:#ô„Ō2 ęŖ*Ģ>ķo´Ž{Ŧ (Ā˙Øģ)v9rĖ.9™ ¤fžĐĀ Ņ\áYÎS A 1RĘ_ŨΊķgt.ŦģîTŦŪ¯rĘûŖÄdw`j ÎËCs­–†šaa<œ˛‚åŪ$mĨք Uģq2ś^ŅÍGp(×P7XŸQ=¯ÃŅŸĻ…2ĐŲŦpVVŪx‚ĐÁĐö˜‡˜šKEÄTA|ö„(Ŗ:›ÁR C}Ø/V E§íIOU”ČčíŲÎĨvU5÷ƒH‹;ž‹C…Į/Îa×ķ*ŋĸĀĸ QÅĮâ2î„ Ļ›€€ĢV9ú ŌJŪææå°ŸËí:TŅÄ_Öiތ f§ ę(°§ĪĄ˛žŖė§Ö´í—Ö0ÃB Cđ`öGįАŒcļ×îÂ?›ššû+ī2úúŨ‡XÜÔUâs=ƒēA¯›WáčĐGÍ@Ž ͚ßĩ?XAM7'VŦsõA¤•ŊÍˑųUĨr?8§Ŧ‰LY”Ä: Qa—Mbv•Šs ).ķ;ˆÖ¯pJɂd4ž‰Tmē!§ĩÍ3‰–Ÿh"Á” ģ1e+ûŖč˙cŗā׉Œž .bŽÅ@k$ō$Ė4yÚåÉČÎb8ō"ĢŽp’‘AŽ,÷;=ÅO-—įq ¸)î˜>Îah€Ą0fá´n<ŗXŦ§˜k‹ēÁ6ŒĩWuŖīú{Cķ/ūIžĀėuųræv4AvŽ•įy/Ú4¨—‹=Ö ū|Ąk]_dū_oJ•×ǟäõ=ŦûéWĻęd . •@îŊ§z ´Ëč3¨baEZ˙‰üįî9šU`r Ē˙€ôšįq/'Dbŗá¸´X]ŖĨyŪKö$*%âÆu‚˙Ÿ)ü~ū˜Ûŗ†”a¸ŨûQ8ŧÖĻ/B0‘0Ā!5‹5-?¤.›i6Ėąg5š‰G w,ĢPÛT–,qO÷ ]ĻH ĨĸāMŊ#W…W-ŠōŠ)æŠQCp°Ë2ÃĪļåR…Ļ`Ö`l%Aštà0‘ČËbV6PǜLü§Ž V­8‚“åF–1ˆn‘n?Š÷ŸūYžÁũūđTM4ŒÔMJWqé%9/ˇĨ?ĘëÕJä#ß˙!Záķë)0Š(j–ƒZõS!e~†Ąē cx'ퟍâ` āķšÍäåø=Ē@ŊČ*ä ūfĸjR¸ģLËīé€;uCŧ&:\gĮ „Õ͑fĐđ ¨Äß$W‡-•ŖË)yž#Ŗ'„)at¨† *pđ•Ėã TíŽBÅöFĒ­ĩŽåŗH˧kĮ1ũ‡QĐ0â!Û›§HEĒŊG*fƒ×-} ‚Œ›MøL.̝2ëG"ĸY‘?ņ˛Ōˇæ Zá{ĄĶ á7ŌÎ@_lđ "8Ŗõ%J‘†gÌË(¨ŸãCŽĮ[f ÖĐƝ$ūs÷,}‘ \叒 ėY™ņb\,ŨÁlbWŸu‰ø–kLKzíņ( ŽRbĪ[™ŸT˘mŸb&EN€ęŲČ ížŠÆ4i*˛Æ ōOį?r… yƒŸû"‘:cxĻ/`}ĸ,1ƒË>Ļ9ûëЉƒ"CŦžØėCgˇpg[bv †>)TŊč¯hĨ•%æÛYc€ŽŊŸ ‰GķÍė1ũqZÛku‚•Ū@?%K5Ļ%Ŋvø”G)1g­ÆĖΈĒã•K5Æ_ ÷.7’Ŋ–XŅãø]DĮČöiŗĐŠ×\°ām`ƒ1æĖ.ō†{T+X7]Ƥā‰DV7ƒIÂ.Z)Pâ ˇ™”Ņ0¨ršbQ+Â86#‘(ē{ÃÂn  ˇ$k$ ž&„?bZƒORĘ,â"†ÆÎW/ :J­Ō0Ļå7¸™ƒ˜ĀÚßTt…¯š?™‹ÄBŽ!Dq 0~Å?äĒXū¸›ĮķOō-”ēØh?äl¸ņ¯ÄĄĢ&č“"EÔßUųs+>ÔXqc—é ÍØ˙%NŒ˛Vŗ _rXŊÍ#ü‡‡ų2cíŧ5ō$íĖîw 5}ᥴēĖÖǘč{?ČŦg1™pĶO0ķ{¯ÄtÂY’?¸USĐîwŸ–‰–“üĨĐJWĸ‹}æUÁP‘Ėkoōā+ķËÚŽŸđƒö‘@oÔĮëeŧĢ)g5Į5ĨĮ lńĩŖÄųŒĄ‘‚Ŗ”u¨3ýŖC˜JÆžˆ9%wÙGÔĖæĶ*ØeČ&â.ÖkĐ%Üë8CR¸‘Ģ:MډjeÔ žKמNfAp°,¸¨Qũ&p¸Åî+™ž÷¸š@ØU ĮäbNwŖ“ŠjT# ¸YõĐF›mÃ%—Q˂ũ`WĘVībÖڕ)uŠ•–Ē„@„‡Q’G‡‰Ņ1Bpã3! Čs]ÍÛÜHšÍË7Ä¨ŊpÄámî rĖDËŠu2•<Æ WX˛Ō”—\`7š‰K,äĮņ;ā\Ÿ2Ø58 Z% 7ŪŽåÉôŖ158p¤˙Ú VļhgŠ_Pc“§áyÅçĀŪ0mœŪ­/3ʇŪ,4Ž5O;†Ã| ‚AÃÎZĪ4”'i—ņO"˛Y 3Œ2ÍoPW7(Õ^DWŋ˙ȧ~I'ÁŽ­wFû›y*āī.ÜD/j6í’KŠCļđŗH˛‹E=§üŗÛąĘ ŋ.oTM"'ÄŨ˛ĨS-/Ķ´ŽG˙ļ‚Ųé{̍–Rũų“˙ƒÄO@.kØ ßÕ0HÍr—÷|8Yî¤Å1äŖ>Žßņz´%J^íjG&Ŋ_Čâj(Wĸ ˜G\âUڅãF# Rc‘ųS1VÜK‡īą'îĪ^ōGV`+ŅāBHp­"ÛwkiŋMã2JiĪķāˆĘ1tüo˜Ĩųû?Ov¤æÖæ–nĪ5īŸíÁĩÛ˛īB RE[‹lėPzįÕh ū§æ yķ>ė†%ÃåËg ÔÂ@Åä‰y$ás›%—ŅŒšˆ|åˆÅ!DšrņļL!”’ üĸüæ“\؅UˆŽĶ˛Éx`ņĻéŠM$ÁE{:.1.e âĐĢՒÉ2Šđ0o`×}Ɔ˙ÔoV Ė’ļwŋĶæ.RtęNXĻûí™HH€˙¨ĀéNUéôøln€Œb"b B€wfMÎ( Fļ$ĮîĪ­Đˆæu:Ķ,ŠĮe#zŋ¨˙áį˙õGĖiØ*$ŨRøÁ&j†BąN˛°Nh˜ ˇD„Cü5I(ŪZĪà |UöwËseŪ7 €đˆŽŪzf‰$0ô§Ė*ĉ¸ČŦ:ĖįĪV|ŧ‹€š•ũg¨:ÃĨ–˛ŦōØú7OĨœ62!öŗ×AßîlΡ¯â˛GO$?8,ß÷Õņ_,ÎQ|‘ŒØ8 "ŦŋĮŽ?R˛›jü<7U"ūĸa:Ÿ´ĻĶĐXk؊-4FM6&ŗãāĶ.8,—5uWE!k y3)#az‚ÄŽ su΄aãŋņū m<`Œƒ(BŽ*Ë3Ë*žÍ…}Ö.°ūĸjYŋ íŊļęÂÍlíģ'ÜÎųcË;omÖ]ƒ›x@OœļMcŠ`>!ŋî@wûwá1ÉdE|}_e‘đ¸BÄļąĪgļ[žBe¯['Äqå‹HÉrP%ˇ-ÎĘXLēKoĮínûfģHsdÛøY*‹ŖØßØđe˛ˆė%߁äw­×ˇ¨Å˜bۀIŊ†[đxZuẻã,8ĩ.ģ8`Fń `ägX!Ûė‘’ÉĶËšg64Û#’ēÚu‰4“‚wgžĀŲØˆˇl °ÆŽ[Üŧ^Ø/ä}†u°mÂÎÂVA§ogČČ „ØĀIĄé ¸ux†7EÖĻN/Ē—N–w^Ęš%õ°3ãrCd2^kņ Á´™GH ͊21`#E˜|.™gÜ6o?čŗ€Äī ÍöF Ûėąõ~“šL™=ŅÎ7Ģ0,°Éä_øBáągėoÉ9uēÁy¸î #c6–Ũ‘Ķ2—dÖ ØĀeäŋ˛ČŦŗpŠáø{ æ€{ÛˆļS!=‚ĮōÆĮ‚ņė˛žė„HŪ‡ÆŽÄõ?hՖ[,rÎʏ$|CËKKKdc­’ĘËŊžŪ|o ÔI‚pe8܌}ŪėÄ!ĸäá̈ûƛyß´šuP°øxÛÆlĀØ`Öíü´Ë#–߲1#Œvdá—Fߡ…äO\ŸaÖ\ė{yZM—#ŧœÆ <68Œ#cîHk;ōīĀä˜Ã°g-ũXļĪę7TĪ ˇ­Ÿ„-ŲocÔ\6ßšãŸy KĘ+ÕŅ6ß|zJÛ°Ō’§)߸°|Ĩ­ė?r ',;aíÖCŋŦMÛbV°į/Dˇ}2Â3Ų']ē R3„šXË걎üäjã3ė{gn'Ģ+ÁiĸĮØ^™@O¤3ũ͇’ŪļØ ļķĢø&Ŗų í y2:ō !ĐĖÛWâ-ļ˜D#2pak‚õ}1`tØq= ô†'`–NŸíÃˇ“÷d?ˇ˜“[4VZ’é6Ģg¸1˟î U ßíÃ˛l›ue÷-æKíÎú€lKPŽØ’å#ÛiĀSØīąúŒ_¤ŽˆũI×[R~ ; ?ËKÁr†ĢŲ‰k–ūōr)õlB\r !XŠycØQšŌ\ËĀäGŗû$ĄÜŋYęļRzOZÃÜ,˙˜g]9#Ķjã7w~Ŧæąë=ŋ¤ŒÚ=‹Ĩ4žÍû˛ÁN7ÜŠž_Ņ˙ģú&đ˜e#ájėņ|‚to]ę†Í¯ŌOFɡŒōF’î冎'“yû.'‰#ĩãØųHG;8âÎķōG›3ßÅ~ sü{üyVžíIāŋ˛Õš#4´ ÂŪŋ-Sü‘ømøÄū˙ĮŽËˇžŸŸ´†\3ö2îˆĢ;üžŅf\LėĄÍ°¤Ī‡Āü˙¸/?îĮØņĮvBčO¨EŖË/¯û‚čGOËũT§Ô7ßVüȜ-ęÜ1ɍ’äŦdūŖÁaËAđûđÍÆįØ×jŗųm’8˜[1™T¤•õ`] ÂÃİ;Č×Ë̍’ŋV0’ØLÉmõėlĀ8åú/ŠÛ{Ëö•ļũCgŪA8ĮĄ`äɟ¯Å°>1ëūr@'‹šc;-âúŌën&woããy„ėžGí. €&Oņež~U`üo¯ĮPl=Õ§^Nx“\.–ũŊCŨĩH~­C¯ÁÔi{°ō=•ņ•y=X'n—o =$˜dē^A{&¯,Ÿ'i$fg,ÍHāË~ä\$&2_Ō49hrŪXEĢ“l“ņ×[îÛ˙Ä&!1AQ aq‘ą0ÁĄŅđ˙Ú?ķČOŠ{`ŧ[ĖŋIˇĢū/@@ú ļ_–É0rČ2Aeĩđę§â{cc[$€–âØ||ģņ‚ŧä4!‘ĐŒ?Úü,́›Ãõ“˜\K°Ųåú”ųuņš'Æmˇ÷đ6€ZB—gZŽ–ļÁ)ņí!9&üh?ĻúeûđŋˆÖņyOĢS6 į–v^6]‡ė™fĮš&ҜY'Ä ėNũIåõEÄ(O˛læŪröŋ§Āé°ÔDû˙ŋŒ6 ‘ÅŽûeØmš BßBΉĻ6íÛO—ĒZ[c ͐ ü]ŧøRZOņ-l?V?$Ė"Å"˜ŋēCŊyōMQȑœ‡SĢ;đ—~ĢaĪn%ÔÅļ|nß[fɋ[RÎ]Dä—éÎ> ,Ãb]ä‘Ų— K…ųŽ(“›ü'č_Q•d ã~ _ /V&lËHÖõ@á}ĐØ˙>ā" Á éÛī,Km!ųZKŗŸãÅÁg$Ό{–NëØxõ ŋ‘ā’ä˜Öâa ëhKŪBĢ}!8žFKÖB5moĀž[ąvJe/eÛw 'Ûę;AËđY™uO„čä=2ø•q€öãļ;m\2%ĒÛíųÛ&ļ//öß ĸAeÛÁ‹yeĨîéä˛ZöBÚÛm팞öŪÚÄû"íÜ}\Xī^H÷cvöũ>'čšāäErßË~˛ ÄŗlĻj‡—,č—â@&ã 1õ:-´âŽÛč%Ā"MąQĢ„vPļ%™drÆ8’ąŒŅ]—ž¯õJff;¤ KÜŊė‰'īøN.|cÖÁŧŗįĀäģ#aÛGeâ$´VŊO ’ãbû‰dU3o-:† rú¯å‹0Ÿ,ŗÆ‘Ë…nÔžŌĩĘ)ׯæä ¨ëigŨŸwFĪEļëcÄuH k/[>Jwֆ˛ĐīÄėũąĖ"˛aoydvíHčnNI‘Øjļ;1ė¸mļĨ÷#†Éõe˜mõĀ\EtĮÜ!ËxŦįՌŗRŧNu—EÃ<‹RWˆ!ˇ ÆŪmm×'Ė›…Å×fbÃŦiø^6'`6UHŋ§$‡8Yļ÷–ūã­oSmØ5Ÿ ŨøŒŌUš[ęúÛw0šnŨ¸0ŧŨ2×c MLĖ%sÉČ.‹27ovü@ŸÔ2háÎ ø5–ūļ΃ŊŒbųrÂõ''Ũˆk&`'ÛmÂŪ°‹Ŧ§aũŊr‹3%ŽŪ>õ?D¯o×Ėë€X:ļ{#íė–b­ô[d]ːę6-L.ˆė.|ú‹Ŋ€É`Ä­šr,xģíõ>_qÎÚęÚ°fŦnw÷~a °™g h䰀ėnÉËeģō[>ĄŧF;!‹ˇ|BåÔi#"Á ՛‚Ú tę]|>Ųmd—ífũX𕠧Ũ¤ÉĻL’\9rį vīKÄ,ėwø€9Œˆl<ž‰w Šë-ųųkÆ{ji:`Ų›.Ûyėļ/u˛ę˙(ũˌØvxČë ōōŨY§mV öh2ײž¨ėŧÂ9roĻæz‘—Õ"â3vFá9ŗŌGXNĘ[°†mŅ”×Ë6؉,z1ŗú.ø[ጙyČ~ŖW.Ņ„7Ģ[ŗŽĘŨ¤'Ũ–I4 zHšũ =Ķų}ÜãÛOôŦ[ ŨåŋIc™:PáiÖõb§a<°‡râ-<†˙š;„˛Öäŗ Ũ‡ĸū˛s~!ÍE09Gv5`W[{r~Č,Ž_’žãĄ•Üúĩ,\Üã?¤ŠŸV”üŊːŊ†˛Ęa~ū߄•å̤ĪnZoŗbël/C$÷v+xl0[õ’bG‚}Î˙ÔaN Æyņ‹3ãų>v;ųíĢ‚Zņ4I_ŽČĖqžƒyí\#Obâ[öpŽČW‹$ëƒ.!`—wŗrxH'mpFŊ–ü?[ß –wā÷>ßpˇĶøŋĨ˙‰Į ˙våũ‡˙ŋÔŠæÅÉ˙ĸSrū§ü_Đ˙ˆĘžTz^Ŋ.ŸŲi‰s¨R(ũŠáĪ-;÷zqäŪ’K$Ûė^ČFƒš@Ú9-ŋž|ŗ ŋ¤×ļûū,ũäwĪã˙΀5˙?ĮŦķ?ņčˇô 3Û}_üî Ųõj.OōårOâÁßā˙.ûĪü{d œ…ö9Ÿķ9ŗ°Ęč{ ū–›Ä­ ,ü•ŠxXéé#ßú\7ęJŋúY{‹)“ÃS{öģ˙SĖ`üŒuíÁp@!hŪŦÂk­–8˲0Ŋy-§ÜÁŠØŋRé+ō›šŲv}ÂÚūíôģK‚į—DŒģw ä Ū N) gobäÆ~ĮuK7Dą3°ž’.šôl{ĸÎHė9ԞÄĩôĀ™–…búũ¸—ŗÄNցbiË}_izA -ŽÚĮ—ÖėŽr9Æū˙÷üIģøü ˙ü§úū=këdÛ?$ņ%덞ôÃ)Œ¸œĩe§Ā„Č>‡ų„M?‚ ŋŋĘ | úĪâ„ĖęOŌŋ‰ŌŌO ũ2÷}‘mødŊ—Ÿ}Į˛]åĀ[ /ĢQ§Š(ŖV°pƒŲpR’Ķ#… Oc¸¤Ŋ}IķPž§L™œfŊ2õI8YŲpŲ튤Â=ÂãM‹˙Ä'!1AQaq‘ĄąÁŅđáņ ˙Ú?Ģ7 MË ûĘųĘ/Įx°Z˜-Ŗ„HšÂ¸†IJĻ2\&úέ7ÆXr›ĮÂãėEœāØâwˆˆĘat*ųĀx @Á֏Ķ-Á[ŨÄvßYZ×یÚÖûÃ\€¯d#‚kž&^ ā t\Oĸa"Ns•Å8üį0†‚\Ŗ]aŗsd>™0ŧ‰ƒiÖn‡;į1üfÆž°……ī÷ŒĩŪŲ”"üf–šsQXJ;L ¸âÔÁ@xįÆnČaÕ qZí͏ɚ;đa<í2^ĩį§>üaiáuī *īÁRĖr‡ãÃČdA9ÍCc)P0Ķŧ0Zī)Ãx,Īõ‚ ”`=ތ˙Ė÷šßû°-ĸSjy#—;¸Âxef0c§7 sĀŲÆlcˆY¯xæUnđAqI—āvũcĒi­ŗéäAC'ĐÆĸ=#ųPÅZO_īĮķūüiDA5ø (ī'.ŪpĘ-C €ī$¯ã5SŒ0PĀ)Åņ\#€SŦ´ĶÖ¸(ŧį*KŒé˜ÅĐŊā9~0Eī7ÂīŒ`Ū3CŽ1`‡ÖkvÖnÎã/OxÍō–ņübQ `=wŒ uüâ"­3oÎ2 9˛]ųĮŪņŧŨáˇgX4o:;n]žr yw—\ÎD<ãÖ@!ŽyÂd ÄĄÃXŠDĮÛ˃žWŧ!¸’ŧųÃCûĮ;ë#áĮx;Ā‘p+wšQAŽĨ‚Ę!xÆĢū-é´Z1ŠĶŊ_$ž0Aĸܗ§KŗÄE€äģŗzØ`ÍĮŪ_ę{ÆD×Ŧz¯Ņ—Œ0B ŧ,bų_Ŧ)ę3Bĸ0pCE3Hėb~,"ĢĄ$ZŽāÜÖ&° ¯ *"1Wüzū]gÛUlB9aņÚĒÁ  4ūđדŒ¤Ē;qЏŒžrƒņ›WX^´ÄđáŗjxÍ@;pÅqÕE ŧœ1§2ŠĻskģÖDޜdSŠÚ‰ÎpŖqŦ\ƒõ—ŒŽŠ2ëÎŧe ļc‰[–%áÁmGÎ[~2įKÆ7báÛ ›ƒ%Å nGgŒ xÖ"۝bĐÚb§[ *¤{0đ0Ö ‡ąŒ6Ž(‚Ģ‘žrCļ+Q´—QQÚá°ũ`Ë2ĮŠyČÂ6ōķKį ŠāÁô?ÁÁ:5aI*N3ÆáØį""',~Ģ]ŒfkԐ &TĐ+ĖK ˆ9ĸĮŨ̓@÷îę/˛)°Š jHˆ§?)XH Gn9oų)Š´JÔúËkˇ-K9 ämwm&ĩЁÚĩÂ_Šë )Āƒ-ŸÔD›Ĩą‘Ū› šŅíÎûyj"¨FW|2ęAĶ¯ī"YąÉ Ú)^Ōcųŧãŧ,bāOŌc œqœ>U¸RCĄņŽ…į;ߌģfĻFĐkc§šcƒë&œVėāP1LIÁ wx<`B¸Tø2đäß[TÂņį/f C)äÁ„1Ԏ2y7˜¯8,ÄŪĐÃ+š›iŋx“,Ū Ū ŅœĄ ÃTã*Ī͌7tdHĄÎ:ÆŨaœøĀ/œ4P =âAuŸČ\1Öæņ ʸ h2PČæeöaÆŠœęîåcķNo˙ƒ‰ŅšäOī­8l÷^ÚÃĀ Įgû‚Â:ĐŨØ8…7˜‚h57¤˙åŽu\ļ6i͌UŊí ōü˙•{ö‘vøqüᖈ‡`ŦX0hŗTķœ: 5Žĸ Ōunhēļš˙Ļ^Đl€t¤Ųcbéģ…Ÿn*6Üō›~éžđCĸīęQ§˛õ‡Ö4ĄQ‚)íÅčŠ2‚"Š${ëE šš“§ųČZå„wã6 úÍũõŒĒ÷y2AÁhs#ƒ7†ōZ0#6CY´ną´:ĮnĶ&\!æįD͊€ņÖ"ã‡j <äĄŪ3C+Ū*õŧī¤xķ’sŽQņŽ°Ū fjãFH;ģÃIH`%dá͑'Ķ“”>q‚ˆžn°Ž2ƒī )žqÍĀ ģį&Íį4ĒŊeŦũd@qŊ劁Æôķ‡Ö9¸$pŖ^:srq÷pÍÄIGÎ0Ŗg?æoOø.šâvž 1Ŧ°š3ãé€p;Dfư@đËNv _Å¨WÖ=J-H§õæ (‰"pĶü[)N’J‡ōėÕA+•XƒÎV¨¯Ča%¯ÅKh§Âķūz÷"ˆčŠį†Į×|l_Î'Œ ƒ­uņ†;oN“@hʗXĸ¯Ŧdy\69Ė9|Dď0‚zžpJ;į$/Æ2ÉJ'9 ŋX4ÖH/áž`­xÆœ¸S{ۓ·œ~rÔuŠ™Î<)[s‹&X‹Î #§ŧŅ5”h5œ‚ī}kųĮæÛUKšüc#iŦŌ.ôIī ŗ5wGAņŊãÄ´e -ķ˙uŒ1‹` |ųĮmh žũæÕŗŧ™§Į8kŧ$ĶX@F4ī†ÜÕĢL'>=ãĢ'XĻ“œ/öËÃyK|`V¸ãx*Ān&Ūn)b’šĨúÎ'ņįôĒiCŪXM—Ė~Œ"!>ŅYôÜgĮbÃøpNhB…cĄÍõc^a?Â&?7ŧqö>YŪ ō iķ‰Z/cíÖ;?€â$ĻŌ‚,pų90šĘØLlĸ*BÕ EĄžĩ‚į[ŗÁöfŽÅĐĮÚ/ÃŊáKúgķhEķ;ųĀ ŨP€ē}¸ĮdB)dœ÷Š(ģÂW8„öØ1Ũy`V”ÃRîa\c įÎ_F5Ū&=ƒÎS]bE§;n]åÆ5ŠáoœAãyÉ6ņ› §—ŧc°bėg˜â÷`û-ۉh&|áUį@:ߜB&ÁJĻ ‚‹Œ_X~Æ,ēÁį…5Ö\ōQ/>2âSZN P†Žd8÷vøÉŠd@Ũ ŗéŸxëDĪŪ_Ģo÷<ãčQ €ßŗŌqŽ>LÃä8(¨¸[œh­ąĮœˆÖ{ĮUáŅŪSOĢMĮĨ2BŨøĮ‚ëį(te¤œ¸â 1ö€]`C|BÆBĄPeK9ĪäžcÚēNųũdäĸā ¨V€ÁUŌs•št@ˆ ųąQÜîsáG—Q2rĘ(•­l0Ķ-Ÿ3–­<.đ׆T…ķŽ?Áî…Ė[/č?;Įj(SĨ×čŧa¤ŦŗØütú\E*ÎVǍϠĸT°Ę#(tŸŧŅt õ"‚Ô‚T×ˁĘ*ŧŖÔĨ-’A …GYPd¯¤^ L:ņ+áÄ ŨRĸšP}~–|S7žŗgGŪ;ÃÖ°ņ‰7ŸYj‡ë p¤PŽOÃ˛oÁ Ā+g5œFē‡8 :ŪNX\įK;Ãw!úÄx0Rē üãŨ1t T'BÃâáä E͉H&ÍmcŖÛ…T”*§Î0ĩ­҉„ ’œo  lŨĮ-Đ9(ysox Ū4<Ũå˜7ގ‰Š2°ß­Ī¯œ”J8R‘ėŲû ŋ%mûqęđÁÆ˙‰‰&#ąĻ!CŒ6 ×Yinņ‰TQi”6lë°IĄÎlĐÎ ŖßœnBpcĩĐa;oÖ"Ī4š§ž°ĒáÆ>ŖNɁĨåŪ6o¤Á=ŽœäåķūŒuë˙– “h\}†<Ũ‚ąíãÂ&*ëč”HÃåĻēũĖ^€F€ŧØŧ˜)ß=R  €ÄŽB ?1ŋf0¨Ëĸk<]T+ Žđ‰ŒqLĨh’ü8ĢæÂüaž€Ÿ raęÖŠÕöĖ;¨h?XdI&“Ūį"•|ĒbŖ a/ĐlĐ_Æ#š$¸ę8ŊdĘ*ãRt÷ˆ;<áI:ƓŗÖ830rJõUúÆJq„%Æ5nņĶĪ,TĻ1ŽPÉlÖ#AS!D {Âā¤‰Í‰zB|ãhe"uƀåîchû É|ēÍÎĨ7&¯ŊáTŠß‡`“OlÉaTÚäé¤ī4ī˜ņŧXŖŧyŽ–÷ƒ¤ëd šļ%.‚īĐįŖj#{„}kŸXF““i(ˇĘ#îfž,oˇį5~˙ũ0•7ņ—uÉŦe:č ˙{@n4~ 3ĩ }LJj›(ž_yb†l9Ã2ë"8(¯ĶƒĻ‘͌i¨ŊaY‰ -Æ É‡īŒPKzÃ(Bo7ĀkÎIƒŽp“oÆ=¯áxVča€o @‹Ž0‰ŪhxŒ4.%KnąÅ Ŗx#ģÂÔÄt­Û€Fá€^ĩõ:/œ"”ŽŗDíOÖ8#CÖKG8ŒCFžōsŧ\7Mŧã ~˛įC´9ÁIŽ]\ĐĢÁ”ÃN-¯d'ŧ}ÅÉ$ÖÖ¸’Ôķ1ĸėÁ4y˔ÕåÍ!Ā딁€]‹’āĸCÅ<'9rA'' į ¤¨†(ēģ !ãŧuĀÄÄföū°Œa6›š´'=ā—vu”UUe h†Ū´}cí @n“ŠßŦÍKMwYß4øÍÐŋG˛?ÖMž}ā•*‡xBđõˆ_gŧf Ž×҆˛štŸ8€˜Ĩo/ã>Žũķø1„Yí/ë,hWö. (ĢąĀuE°˜k\˜N}wŠuÄɸ įÄ)‡ĮG9ņ& ˇ “Æ(äĻZ…Ķ“Ōķ”GËÖSSį!/ŧĸĻŗˆĻ¸÷‰­Š‚Ė;ĀŌ^ōZ†Ūą¸%›pʰ6p91ūNՅæ$ˆ8íĶrâÃD+ÂfÅĀ$Ĩĸ°Ö]8áÃ,äŋŒ00ŪËҐ4úÃd€]°ģđkœ`PTnĶ—ã×(Žs83cvדŦūuū°"äŊã×x”h‡XŪ4črüō÷Œ 'Ŗŧ%DÖ#)5đâëķ‘í)ĩzå˙F(‘IruϰŌ4oĩgã ´#§ã  ī,9oœÂ& T@ëŧÃĨ S.7uOŦ  V˛BĀŒv¨¸4)VaÂõ•Îj° ˛đä•Hīëœp•M3Gíô‡%xßīLkÂė zĻ`Š!SoaųpÍM…Ō{?ׁä Ėô/œgģã5 žđhøaK­Ã$ŅN};įXŠ\-€ēаĮ“„˛L'TuøđáV˘üJ ˆˇN˛A›Wë7wx‚Z†ą+Āg0žÄ‰§ÎLfüˈ*huŪPšo8Ö4)Î Ánš&NÜãCŦ š…Ũđa*yÚËb‘†Go8ÆŊL<Ā@: Š^­XÆđüį.“8‹mptHę¸å(Z~°Ņ 1 vޞ2.LëĶÖz^˛ØĶ•€y\R"Âų8ˇŧTܨ–`Ŧ6ĀEÄē4q@”x*¯ōá%5UcÖƒeââ4 9ë}äõBę^pˆ)ÎÉôæĀ¯ož0 pĒ&näĶŊ¯úŪ%hĢåĻ€č1¤AYģCŋƒ bBŠ6ĻáĮ8  jĪYklRî}áĪE&3$ Š.î6$ä>ušēT‹ŧ…Dx7“ßY>aT'ĒiųÉ.ƒEųÆD€ąåî\5%‘ŽŨI…,bLĸ ×9´ÅĻš‡y8Ģ_8ŌAŊ—xX„­¤huGãXyÍÂIė*OxÄUĩø}áb蘊ävŊNc6đ2Ÿ2ũâP“쎀0čĀ/ëŲh8/3ķŽ:Ŧ=ôøã 8ēFy™A@š]îBšˇAW2ĶŦœ‚NąĮöL€^ŧamTkÖX›.2'6'‰ˆ.5—Ŧ*Įr?y! 5ŊኊAņ•kƒã ÍÕ_ E„\„ōU@\& §.? ]Œnū1mëX,vy9ĮāíÄߒãžcī9ŗr:Ö=ėÅUW&NÄÕČwėp;§8æ ˜sŦwHÄãAE‡`á$…)Ŧspŧ8paeōc -ÍáҜ(įxīĐĢŧCĸ6ž0 @/A1xO'!Į`hÜFāR¨Gķ„ ƒQē˜BUĐ|Ė'’€‚s÷‹Fy|¸FIĮīYŧØ9ÃŪTV‰.U€Qƒ_6ȃhÄŧp$˙qã Û_|ųQ^aÁyĀ.” Žé>PŗįÖ7FšįŊ¸HBU›Kč 'ŧ vWī^pœˆ›IĶķˆâkMŽÂ.^q™"€īÂWŽųpS!Sķמ°K^ŦÛŊ\7ĩˆŧį7*j“_?8oA,Îũ¸ÆB˜Ę1g[ēĮn[Κú˜o°žĮŠ>°ĐDރˇ‹`įŦUŅžđÆ†Æd‘';ÄŪaŪđāõNŖ1"ķÆŗy˜›Z˙X$âņ|ōâā ΅ŋŅYŧfQ\PߖûÁyŪ •‡œļŽ=Ōân‹’Ŗúŀ(oS›Æ3ė˜`0qau5ķšEã$!Fą"UlCœb`4`qŦÚ!6dČAŠn`<ĩ øĀ=á˜f]!käaŦˆ š,“‹Æ DA‰ ‚mÁÚä2`ķ“„ŗsœJ!˜yÅāŒØãÍCíp˙ƒíĮ$jl_ëŽ]éŊ`âä8ōÖ Ņ)§O8gĶtߜÔ4YųƅØēC–{-2`—YĸÄķ1Ö‡ü?ŦXĩĮ/ã +'øĀ,ģcoŪ\Aĸŧâ 9SģIŋ-æxĶ|ŋ•xÉt~ÚvžN78FŊ.úfōĶčfîX€ˆyG€¸&ŋ§+÷*ÕŲNŖ}c<•ķdSđ֏Œ$Ѐ‹S§ņŠŖ;íŪWœD |\Ōui„ī4E:ÄÅĢ L*„ŽđĻK ×˙į•umĪ<|fú6-ĐûČ`‡Ŧŗ•nn‹KŖ!B ›:AĒôܸ Ĩë—.‚H›w†¨gzĀہUĒú{Ëą§+ã §#Ŋâ Įx ü`‚T'Æ oåÂÕm“å¯x"%}gÁŒˇhī QĮ8FúĀRÕÃĄÕ‘Æx^P9ËLãŦ3­y›™ŽÍĮ€×GŒ˜ëŗËÆ ˆwf&Žb—zœ”1D۟8m5GU™%›¨Ŋc„P¸Ēac1Rv¸:Dtä!mđãĨEĮBҌŒô1´Ŋ/–Ä(‚ŽųĘ€§`ë `Ģ‹ß#pĸ¯G•ī~0RÃËā?Ū%ōK„ĢÖ:RqČ&–ø÷–ClQiŽ Ôã˛§n!›åÛwŪlk ÉC @0Ģ`5|ä˛"¸Ÿ_Ît˛IŠÁŦaŠXa¸ V!zųôa;ÕPZ‡Fē k¨ÃwĢ•§fŪņŌŽ;ÆJĸčöcâlž_3jqâœÄÁIR;ķ;~ō„&ö†ĩ’lš#"G‰KÆ# QŅYõ–‚°iP÷īBÎ'öāJëŌKS”īļģĀvRŗą%ũūą QĨäN”h¯Ė*œ†FĨFī&ÄKãÃŪQ!Ģņ€@JĪ*"ûÍč"jīXŠ+›ë2 Ã*SšcˁÜ"d…€†Abžr$­Fôņ„Jh‡8øBrŌ}fōj¯ôs—TU,?Ū3•S—לc*/=ä4("†öÄôī5¯jG ŧúz˜nTZiyøÉ¸NWgFy|b鱄¸Ü— ‹ĸYŪKĄ xUŦd:}}Fáƒ%DĒŽæĪŪ9B,‚Ļâ‘yîžpw ŧĸąÍxVŠ ū&%56ų×ÎEo ې!ØtÎ ļ Ąë|æ¨(RųépŠĐØŪ>ķ‹%ņ–hÜl W`‰áã KˆÛh~,ģķŪ Z˜úĮ⠁ũâËé9Áp 1„XAŒ;)}Åū1B6BwŋëėIĪOügA ųÁŠž @UuÖ8Ô ´aī¯xˇå@=×n)Š_cæÛņ‚˜¨ķēņÎ2¤v^=>ō9 ŌEųp"”Hī03Hã¨Ë@ÑnfI¯8)įX°í2×ĪxīIc„ †Iã@Ŗã5ZĻh(‹Ž69TCßÎ9Ž5Ä{ÁÄ)ˆPp–‚¨Ŧ tn0ĀZAŊāG˜Y…o“,^¯81}ÄR¤ÕÂņŽDĮ1a`6Zå¸w†@¸ÔˇS* ›d&Žƒ—úÃ6Ä]ûÆ!´:žŨā 0#ąēũcĢ`į)ĐZšÖĩŧ€I×õŽ8™BáŽŌ~.Ō)… + –~5…48[+ž°ģ¨œ´8XkH6ë—]vcÔB ŗžņ€mæa:ĄWČ}o6YJj!Qąē_ÉŪ0Œ)w„KĮŽbhAįų2īHNtëīŒc››>2€ĀRíNįĮė ąæzú˘éë}å*ßŪXÛļ@û‚¨ĩÂA–{Â@A^LLÉĪ:ÉU$F÷cŊoY2X$CŦī j`Úđ•Î+ĻkūųÂĀŦ=hÃ#J Ē÷’ĸWkێĢÉ.ŧį'č Ģ°î­?xŦE å ˰úÆa] tĪ|á Z;Ÿ=]ë8ONPæy^3SƒyįmĒQÅ q“!ĐbˁŠã;Ž(¯Ž.ĄGœŲ `æ¤jčÂDt͋W{Į „ķŦî§Xî–ęb H˜ČėmqÑXaƒšoŦ;)nÁ|beŦ1ŌĶÂøÄChjcŠņ‰.™ĘE'ÕÆČ sįŪMá\ßcÅ18h‰>xĮŌ%olÖCaāäœēÄpäUt.Kˆbî!įa šãn ˛&¤§ũīØĐŌÎ˙ŧ¸hPKšÕË d ‘7XÖCē] į˙0kŌá c¨ņˆaR$hŊr$đøÉ&8HŌ:Ŧķ.  iķ’o0°Wnh†¤ŖŊO9ÄĸyĀmŸ9Š $ä­īú ČtŋN"’€ž?%ü3*¸Em”įw}TÍdĮd!īŌpÍ>0;ē8w=ŒëëĮ:˜37U8üd‡`ŸnéģWĢü¸‰•ëOė˜vĒoŦt–=k7,•Ä yŪ°;!ĒĻē/Ū3U kOwEۗTŲRŲŦNd|ë7 ĀoX9U>ĪxņĐ$đg#Up0 ĨįŽu–ĻĐŧÔ:=aYM‡ÆsPĄË‚Øæ/8Ú¨¨÷ư“AãõˆjŪīj€Č UĒâ¤70ØhžqMŽ@'.Ļ[ÖJ(<ã֐ī H.ĶÆ^’ŊåōŊŒ„h  ‘ŧAšL2ĢZrF(˜7nņ IĖĘđGÁ¤ļéË"+™u–Ŋįd'p1@ s.đ  ÜÅY'ƒ“EFžŽ”?ŦT:>|˜×ÄaÕĢ īĄ¸õ3ŒžU­ėNúĀeE]­Ÿƒ*l2qĀzã BÚhŽŠöŸœŠÖėo‘˙Œo•Pi›āM˜¨ 3%œtākEŌ-¸ūÚE I>%ü8h"O/!Ā/Ãŧb˛Ąë“gŲŽ=ā5S6ŧb¤ƒx\""‚PķĮŧd$YFJpadĎ"Áī‡ķ;ÁÉËVl‘9ņ†„Ē>'§z6°˜åĄU^q˛…9ĮЧOŧ$(N*œR˛zCWT¨Â§ŗ!€Hē˜eJqųÃzÄMĄļ.s‡Û6ŦS a\Ö´eĀĢŪPÍHD§XF#žŊ➛×üü`K ÅMâŽĒɆ0"8~(UWK…ltb<‰ÂyÆņË^§ũÎm ˏŒÜ-=āĀ“kõT,axõ’@† +l­|áĄ:à ‡WËÄá 4ŊåÁŸCŗ6‰ËœÄ•qˆÂí{Į`Ęa…˜tEK ÜBĻÍWltô(ūđŌđa™ÁŒíÉrį"Ājv8lČäƒAŋŧ¸ģhųÂ(ģžÜģ4mĘÎ Ü2pväáͰ^‡ŒS"FôæÂĄĶgQœb¤ ŽPƒANÜĐ-°ĒúņšĘą…Ūkp€œoIøOÎÉčˆŌy&9¯49KųÖvėMëņ…~nˇoYķ‰Ą,R2Yī‡FDGe&†đũ` …ŽĀÃ1ˆ"t×cäķž2ÔZ|yœy9÷LDŋžˇŊaib&Ŗë $7M°õ† ĩWT°?œ%*H­™o`ymŗéëÆ%qVːîŊŽjxP|gŋhŦ "îŧâ!Ī—ÎJŦõŽˆœ@ôw‘(dúËeŊmÅOÚv:}åXpžūqÅä+ŦĄAÄzÆFõã‚ĸˇ%ˆ™!˛ÍMåˆ"ā÷ˇ špxå¨:†5ÆEˁ‚ûõ‰$Ašđđ{q™ Š1_ՒHmyŽ8Ō⁰å|˜Ĩøf”ž-ĮXšcŅ Uq˜Ų úÁČ5Ä0õÆ‚råŲ‡Ô‡-͚5Ŋá'9áë fáķÔē.:,°_đKƒEEÅ×h!ŽŦDŽŒrĀ Ŋį&Æ;~0ųsAȘđ˜#Ną‚ĸe™¤)ņƒJĨÖ[ą`ŧäÃ7W€Į Ķ‚Ø‹§Ŧl"4ø|bžK‡ë”œ’ŧ˸"\hĀ­Z+ׯŦQŒ—žxKĢ@ØÕ>Ėmĩ A{kãl;ŀE]‹œŲē=fÜęWČâ>Ļ_ę&¸—"ʨMÚ%ũâAŖØŨî˛J饠‹Į)øÄÉX7ÆŋiÖ1Āĸrė‰ã Ļ‹ĸ“Æ:4F(ŗ”éˇĐe°×ŋŧ5bˆ.ÛԖÅô›;ËÖäķ ĮF!9QG^ˆlyŋXD{Ļ4ö‰Sõ€Î €üzLfG ŌëæcˆōĘwv7øÆ1Jiƒõƒ°3J-ī dX(P×Kã1) \øeĀ 7p#˓/īIÂvbŧ8(ąNl5ĪųøĮpÆNWGx^ĩíÆ: !Bv˜č „˙|cWo.ßˈ )÷ˆŖiš÷ŒI Ę{øp ]یĨ\=o˛„ŖfQ„s+¯.8ĢqŅMbÉã9ûÃ(z˙8%M6b(AVL%F"[0yTäÁP‰‡ģ‚J)_œdŠ+›Ü\׌ŪđÅíųÂpˇ‰¨bĻĸ,ÂC&ĩrĀ4ËuŒÎ=eĻlÖ^…KpÜlĸvdę§°ėĮå*WXą8čh"ã—9n H8ŋŪ1ļĄĐ5_´ÃŦˆIjMmŪ$ÉdKōS´hņЈTh ‰ÜõÄ#&)ËĪ8ÁSČjz˜%i īĮxnø"Gđ?œÃˇD=č×Ášø^.įÖļ|&1’đĘŠŊv>;”ËéoË ¤ô8kĄíĊ)úZn2å ŽéWËũa[‘Ũlâ<Ŋw„â“kΖpÜ1ģDq9ŠU&ÎÃ‘ĒĄ<5_ûŗ7*o“ޏw„d@Ū|ŖŦ{‰i¯Œ ‰ :ˇø§NAĩ:GÉ_9!ƒ¤tĮŲ„CA3›äÜCÆ\TR u–*Vâėŋ7ļŅŽPÍ&˜RG‚d „ā;¸Pģzš.‘$įd×āü〃j\ Ō ÔÄnsreŲ 1% kY Æā’â…ˆ+õ‰Pp›"ˆäSŽņ˛¤zr8äš8~pîoWN!1ŽŠĘuë8ąī7´ĨÁj¸{ŠBR§øÃ,ŖĶįJ[0ƒ1+ĄĢŽFV@Ū1V…Ōp˙ÜāEPĸ‚đÎaŒÔ‰ĄĻÃÍãį H;ĶŠį* ĐŲ_œŽ-j´Ō ™jėOŧUhˆÛŠBŌ"§¸Ž4(’ģDŧ7`Ē vôw9ø˜l]fĻIJŽ“)Éā÷āöīįˆiUĶ?=d°‡ĩo”7Ž^6Ūãۉ\ÔB/RųĮŽézkŊw;ÃIJ§‚āŨž<ãGeáĶÖˇÕëDˆ56׎;ĀQ7Šæ”ĄßFYMoŧwZxC‘øÍÄŊHO~Oɂ(€ŨmģŸ4 ¤Î÷”Ū'Á"ŦNĐč;œ|ap F8p(69JltãˇŽ Ģ$;]āI8Õœá  ūōbD8ÂN*ōåļQâp|cް€ û€Ėv}‹„u&*ėm‡žņeÖĨôÎķx ÕøÄÔŅŦá@œ]cĶ@ē0ßbėœá2°āq.;˖ëÆ(TüälĨĢqLÔ0'!âkŗ5ĸĢ1kĢ/=ä(ęˆÎ1”HČŧäÉY“Õ˜Č"uŪ€@ū ˆQvsŠ$ëļ´<˜()žœb-*õ’“/Ŧځ9™A´04!ŗįx< [ÉdØjœ8D€ ŧY`Hžũ˜×´°qVDn、Œ:­¯Æ&4\FŠūíquÃ_+“8H¤mÜ%×ãĩS^GXÄ @Y¤z{úÃpY ĻįŪ4ĩ’¤Ŗėáų4!;pļJŠ÷GI^â‰ģäNĩ‘J.ÁÚs`å 2„â`§Lį, "‹;ņD‰ūđ •’Ō{+†pH%ņ6}áĶ^¨‘yī:‰…+ëe TĒ„žÃ,ą1 !6÷3|"ŅAĸ”tëÆPHm„Fhë61ĩī Ų“vĒŠōČ`’*C@TžÍ‰ß[Ækq*—áäzG]á á]tzøyŸŒÖzQ(u• ŧėÅxMx1”ĒžŧcZ*ĸZÂuŠŌđÆÃ.D~1ų CЌ3^F믘ÔNÄt†$‚b´–ƒÁã€ņˆ.eãoÆ] :ŽRIÆô8¤b§‘ë HQDãÖ ķš E4ã|ã–j‹ŲÖú¸zĮPĸĒtcR SåÄQß@aÍ;˯@sƒ`ŧ^ņšRāI„ˇŒALŧUëŪ e.†xėj'ļi0ĐI÷7•į̃īÁeēŗË†B€ãąWWŗ”Qxœ§öâöųÆĘĢ̆\$UŲÆ˛‰ĶYd 4egŪ2EŨ•Ģ‚†A\YaA^ ôœāAQy5†•Pˆ•Âē †°‡W‡BžzšWuįaäáLî ā€]‡w'xg/ŧv-<ˇYģ,‘<áƒg+ ”•:0()Ąr›XF2°ĐöxÃBÃæ¯Öļôg€ŊŅÖ?Wã#q%ŠėŽ,›¨^ްĄŪ%!MËÍ÷(Fn¸Ž¸ņ÷ë†ÄäÎüâúXĸ5'„ãŒíäy\Áö]‰xDOŧjR“E#üL3ÍÂĐąĖ­õ“įDœZ¯ŗOëŅWßŖ  #¨~U0Ö@#_ī äĨã™Â ö怉HdŨ„ģ<žņ‘â…$ŖÕ>†zÂ{QĀAhøuđŖã(aČķ~|c"jĘLą\v:  oë Bšåá<Ė/đ‘UCŌ•>ĖZĒCoĮŪI$x˜’š]a8Y(éL1#^Dô^>q É×Ļŗdøs„ę"ųŽ2ŗc=€ģīĀ`–ÁØāAn7õ…e…EįBOaÚ8ü¸u Îũ`Pž_^0‰{”ī Hc4B_ų0“^lÄĄōøxjaąļÛŪDá8ŲČoQ Ą(;ë ŧX ˆ1¨ŅG_jĨ?Ũ‚K?xĀP]ŒĮŠûĀGAÅËāĸlį­™ÃāÉŖašg*^Ž\TÔ°ŸGxøá0ˆQáœ8ֈŅņ•™h.Ρ‡Â')Ö īNC€뜕 #"´IŧWĒM]ņ!ŽąÅŠrbEÁgi@÷z÷ŪT‘`ų˜‚ÁPr*Sؗ ]÷Qkįŧ=A=@ @|š%Ā õvĪ|dé]§ 7õ×ŪVËŦ[ߓáĶųĮ%Ūe üZz{Â&Š•ėđŊāŅRãė8uAĢāMæØ*(×Užã„)*EåÍčl/”ãōŊdî…'!ŦO_Æ ×s@UBdÖ‡§ÂxöeȰ‚ų˜hšš&í~zÍ™ÕCQũžķ}@ē D”ā~°,ŪiCWé/ĖŧÜd@4ŧŸ8NOũāV m{Į’A Ŧaüū1ÍKTčo+ÂëNž5…•Ļƒo§›éqŦĀī„a? ŽØíßŒ͕™ŽF)(§ÖŦ" Ŧ’Čv§œ˜M^đmˆŧ00ÃŖ— °<[ųĘ`;FžŠũáCˆ3øŦp6 ũņ„Toƒoį%´S ã)Â/jč=ũfšJ[ÛK> `,QP;KüqŦæTJ‡Æ"Ļ‘šĐ Vđc JÖÜ i÷0ŨaíÖ ¤ō“$ģŪFē T00ķ…65ĢåÅ(ĮŒ",H>&=ÕãŧŨú+M†(Ō/Gˌ]āIÚå–AíÚü摅ÛŦ_Aö×ZŠ@“k”2 uđ<žvŅŪBŠP‹85û™qC=ã.…ĶķŒJĄü2P43ëXlˆųÁŠ oŪ%ĮS‘Â&ÂŗŪ8ŦWãMAXqN1Ę4„2—R)Ɉ‡äœÅĄFūĮŧĄFGë  'lÚī"É<¯Ā_ĪŦĢˌŲĪĮāpIJÍĢä§k V„âđœm‘9öã1JĮ īŪHļZԊ-õ ûđrŠSZ/8A1*ņōk Ô ×ƒ“ņīĄ€ĒENČŋy[O9õqÉ/hu&ˆEҞ?7¨Ũ4 [÷ŗéĀą(Ŧ)IØÆ='ŧtšÂÁiFš'寕­&=Νcƒ¨b5„"ˆh§ÅÅ$ ^Ā{591¨†RT„ãũaų ÚūÎ0ąĢ6 ŌbŌĐ*ŋ\`›[†ŧĮ˙H÷€G-{>qŊ/0{œ}a ˆ]`€Ą;ã,] §\ëõ‹š@Aև Pjkã ƒhë)i=‹Á¯ÜÂ) ĨāÄt# 3AQĀs†t@čž_8 z ‚MīÉė×ÖP‚|d“#Ļņ—ž8c‚ę6œaĸ(Ũį Aˆ‚ĩp˜"÷†ACœ‘! áËT‹ ā!x÷†–ĄT„0ØrRq1‘ 4;WÖ@ÔQ‘ã÷$ÜșaŖ?Úæ†*ˇaÎ6-Ļx&*˜„›oĨ€ņíĘ&KÆnHK8ƙɌôAM 8÷§t“  ĸMŪJôšÁ€ ͤ6˙8VđŖņŧ7‚P]ãÕ ŠÚb08ëŠÍpJܔBŅHĮWڙTķãDŗg€ÆŌQ Ûë72jUąYā¸RzÍŪpĖS”ā­Ū܏ Ä(~6}āûI"đ;áūfj7KÉīßžņÚíeî{ËÔãŪkŋ¸&ŲZ]÷WzēNą(ŲmŽûŨ‡/ ŽÕˇĨūL%€ę×{Oqũd‹ ;ׇŸšÖ5lĖœ#Ę@Öđyõ öjĪûŦĢQ’'ߜh“ ĄäČņy.ME­„é8ö‹žŧĖēyķ—gk>‡$´Ø5kãŗÎ "@H|‰˛“{1nčQ!wzŋYw¤ ȡ¨ĶđáĒIleūō0AtZˇĸ3ë s*glŦã×Á*?ŒTuož^qí\@ĩŒ­Š(vÄūđėäá?ķûĘ`ŠŽ*aĒu؊ĩ rr㕠yɲ;^qúĻa`!ĀsŽ_!¸n/xÃą*'đūpéŅŗÃįRį $]ŠĒrWšGŧqv!/œ-†Ã€VÁˆ„W‘Sœu -0!;vãÚ7ī'VÆÅ˛Ü7 <Ŋ9rhņ÷šâwˇ(ô|áŲBˆ´‚'Ōa¤4zÂ2•NGķ–?1  [īPQ*ĶxÜßÁU×xōķëŦ,¨ˆ#ZŸŦŠP‚÷’@ÕĘø§ą˙îsIEĀųúȰÚ )ˇĮŸŦZˆ=h/˚ČAjûĀfA[ c#€››!Ŗ‘ˆ­aQfSƒ.Ŧ¨qu0uĘ@ÃũØjÁp Ųë*3SVķņūąYĘUšÛō~ķ]#h8g ûûÂ# ƒũž°ŧŌĶ’QÕžŊ{˜Ä„@Ø]/÷ˆŊ 2 @ëĄ-õ… AĄœdHŗo°$üaŋЄiØŋ›úÉ…øÄ ;ņOûŧJGŪ$wÖæRۂĸ~\C žĘ‚Íų*oĒ=ဂ;9ĮEbj5kQīY 8[vkЄВ+Ø'bqN˛9ėųÆTÍkûũæšŖ…ÕÜÆŽė÷H‹Ō†(-C’ņķŠ`„qĻ€_§{ ĄĶŋe•x1č õ‰LnŲčų2Š6Ūäõˆī4Mî;eR Ōb5Žy8ŧģ^đ FráЊņ›.î!Īã8Z,ÉŲ8?x“¤ Äų_Á„Ãp]§ŧs*öã+Č3„Ģf°Ŗ Î Öĸs1ÅP52@ĸ˛á @UøÎ)¤|åN˜ÅÆÕéۍ”š'Ļä*|YąĐĐå\qČ; ›^×R%]–'á?V&Ę<Æ ‚‰´ŋŽllSB×…ĀøŽ?”;įF [ Ģī‘ĒøÍŌG˛īAūņnZĸŗĮ÷ˆí‰P7ŋsŦ°SˆÍ ĄúĮ 1 žC„Öj@į 5xƒIXĄĒø/€é)Šæņ=åp”Ļâą$įĮÎbŒ•`øōáĆ(y.2‚īäŸ÷8O¨@Mnq†Zēkčˇxw’NĒ@>×B!ČKüc4 ^t˙aøÉ$E‡Å6''8ĐÜ ošáœC|Ķ$=TŸļ{ž0ÍļB‰apø‡ple T¯ßķšŋ} ĒYņc­å–*SjOČ&=ZlņBIvm|_ˉHûæ|eđĨK ¤/ ĮŦZĻĄPŗžÄÆ*=:<Šyäž°¤&ĩuLaŒGžfTš”Â ąƒąøw‰ö'°’ëßĶ–Ár[ØņqæR¸ëgŨ˰z‰ĪŒJ‰)Č<#æa–ŗŽįëkI[\ˆĐ5oŪ /|ųÅaPėī5*Ō%(DŋˇX@ėfŋņÅĻȉÔī'ĸJž1ÅUj:0Ų´ķ–ÄĶCŪF5xҁHƒIaāÂTöũáb‚ēæt|āņa6ƒ€ĐT‘įY[ĢŽÛÎĖBWE„]ƒŒtܸA„~1Âč\ jtuŧ%‚EōNŗל,Āú¸JČôŒJŠ„įž1`RÜđhā_™rŲŖ‹D+÷Ãõ›ģ  ņÎ!…Iƒ–ŪûãķŽ€ŊUėmÖ+TąŌŗš“ Ž5F!ä û{Å%ˇR*áŧx­Ņ0F€øĨ/“5Å*SdzG\÷ûąLŲ[ÔãŲį…DįĨߌc´ĸO9ŦOŪ-*ĀŸ ũãXâ“WŠĮûeĐÄæĐĘĖ›Ü:úÄŖ¯Ī‘Î][$N\ččņ€ĘņÎ T''÷‡D†¨ŋ÷8{Ēâ":ŪôJŅīŦaƒØîÛķÆ*Džŗ— rCŠ+’Æmđų‹G`ĮËûÆĒ´Ū¯ķjF]ŸÆ@á›gˇ]āĀBķ1YÕ §Xh'÷d ąÃÔĐ;|āŪKŽÛnã$bIžčuŧļQíwpMņ čbã÷uĸz  šž˛&ÉÂ~nņāwXG:€ĩwŌrNĖcĒhyã„Ø^Qūæ 膹.ũdm¨ DŪ-Ì!8ŲĖįb-C„ã 騍­úÂv›A´árcäÚ)¯ÖD ė&ô|ÅČĩũž6y†$ŠŅ^éãnZPUÅģôī īHņ¯mĸg†x&ą¨Ø˜Đk”$EUÕy}áÛĒEcč^ą?ģÁËųšw(Å8ÖÜ%%ĻŽ”t|)ûĮZ” ˛÷ˆÅ8lŖķSæ`* ØÉûÍũ¨8W”žt㤎EĢėŅõķŒČxCLá>ûĮBd™Ž Q~ŊŌ´UwIŪ€\K€wfŧ׌‰aĄĀâēĮOu”Ǎ‡ _Ö4“€Y䃯8ˆĸ+ÕLq(Ĩh†ŊĖ[(1m§2õktĢ ąĩ;ˆ_ĩ7¨¤[G^zÖ.âģ;tį T Dõ‰0Ät8ĻĐPŸÆ4i}=cĻ™_ In0=Ũ&ũe„č|SW‘¤^Œ\ęäƒC>q–2ôāzÅĮ€ß˛a€Č†î ɁÁŨÐĒu­LtÔ '„ã7ˤƒãœ@h ĘæŖ/ŒÕũۅļŗzīŦn•w¨3TGZäÄĒĀėģ>œĀÍnŊ}ã-kÄķŒP 4=‡x•€Ä°ßĶŧi€Tn‡*#CaߊŧC€ŲüŽÕŊá=Q—w &M^K’ÕälūŋxĄ$Ĩ ŗØ'ã,€ĸŒ @{Eʔ–Ž­(=Ģ~ō-ŠŽÍ–ës Ádh%@œˇN(}ž ķj˜t‘˛¯žp|<ו'k~ōCAĄhu—(`­ķ§ųĮ‘–v0¨ųÚū1ų1v ŽĐW˛ø>°M€RēICãŒH\L{đ|æ–TØuøoš#ËHaú%-Q->ÄA ' ~úrë'O…Ã4ø&:´uS{u1ŋe ÍęâQW€“"“V)zīŒ)!ŖgĪÆ_į:†ah)ĢáûŸœD ęĮũ刅v´Ž’éė×XŅŖƒ¤î]Üų0mÔ(“QSëŦ!{>đĖfĩ9ī0j[DßáÖlØ!|o€Ēp į7b(_>2žÆŖz˜ÅÉ;D×ŗyMĄĸŊ$ŨûÁâׁX|ÃH$0éQ4¯.ɊS"đUpq•!ĨcØ<¸åP@:1ÆCz ˜éöá ˛ņ˙™3€ˆéŊäéąAá֌cT +OÎwG¸‡+ëpč$Îvá‡ᨎ åđ vFXŅ…Féō~đũs¤z<ü¸ãĨĩî0ķÔ2 ;0īĐ cŦ-Ø ķÖ)ž9Âã*€rN´•kĩĸĸPßī¤$6ŨSī¯xŠ¤ uąÍi"¨ģxúã ú´;Ÿ7 ņ”b'ÚŋŒŗ0s*z0ÍB"Āø“ ˛Šđ/ž\Ŗ4ķĀVū˛Ũž…XßâÎ4ɐĢ0üE5â=á¤Đ#)ôOÎosļ!ÃČ^{Čąf8IĪĘ'ã­FmI˛ `š¤ATNŠMcŖŽ đ\DP–šŲö†BˆW°7öĪÎ&ŧ†GÁķ—ŧ<2¤ Tԛŋ+7‡Î ePÖūvå‰ĸô‰äqí5ƒ âfãÔSÂy1! ÆØáx$#ÃÕâ娍ÕÉūøÂfН‰ã(0T—ÔۈPŅ{ßÁĮŪ ‘l&ŸÖ Zz9ôøĀ3 '˜oørč°wEũāZ‰äģ˙ŧcž'qœ=wŋ8Ą Ŧbyõîõ‹ŲNĸvíû”éAGí1 …ą+§ÃÆGĀ@ĩĢüeŒ“Ąúš aū ÃDŠØ4áôä= ŗSAŦ4âkp>qN`c>7„'ŧ;_XÃ9āud~gī ëJRĖšŠĨ{ņāå› xü¸x p‰šŋ8ƒq™ ôšpˆU嚸rEĶH?Œu€Ž&īÛŪ]5-Ũū`”WÔ]qĻ=fŗ‡0âÚcČá‡"g (ü™V [ã&dJōáßČüáˆ@€b˝yÍBiŠã/ĸƒbõ’N ˆ8€kÃëYāķˆÍ+ĐąĘp%Žņ\ƒéëیã:qøXԅî8-ķ×Ū-ą Sw“ãsá;ąô7 ļˇsV€ų“>)|4ČÕMIiw„h:§˛˜Ã €Pø 㙍u“­Ízf#Z„>rģp¨~šh”>rl#Ä~lÁq¤ŌŽâ“Ö@؂IВûxāĖ*•_0cå0Œ…'kíĶú͌R€ėUCákėˆ9Š5;Ŧ_wBLHv:øŪ1˛ēōÚwÌhg(5ÆĻ ĀÁčÛņQõ„ ŋ*Mŋŧ PŅFw0͆‚ų䩅d^<č:˙ĖpCāZ?\fÄEuņƒ˛D…G\øûĀ!cČ˙ŪqŪ hö="“ÚKõNŸœpŦGcnđ$Ũ…Ũ ;Ô2BŽ3œˆ\¤ĻŧxČ4ŠZ$āöā~Đōú>Šâ¯,öI‘ė$Nßįœž Ēē°“âcI—Dë낤 ą9ėÃ&Ãå\å˜8ĸ 5›áЉÍ1âWGđũ?˅í4ÕŪ°B`& ‘ƒëôîˆÆ-Kãĸĸ„i˛ö}c ŗU“Ö0ІļŨcj۔vu $ņq„Įd§hyq¯Į…(ģ^^pŧâ`0ŪáéĐ˙œ!U”°vĒž!ˆ]“į 8(4ĐŊ^qũ.šŗ:Ē÷7Œ~Zå1 eāØüoī'BãWTKÁ0†Aüá&<åË­œ0ÉĐŧõ–‰RG8_gk„Z(Bá m›fpähxÖōîĀŽ:M§hcZˇ€lČ0Ž;Ö K)ˇąÄ‰Ĩ@:UøÆ(Hv+đ7 đ*yí„dy—â?ĘZ0Ņh Nhl`Đ$ķŋŪiŗ65c_Q­T§l­öd4Į.ËcÎîđ”ĸģ‚<.û ¤P§Ŋ<œ›ÕM>M⨕ĐzÂu Ĩ¨§ĮįŊ`ļĸ¤9@LTB"Ē‹č¨NTÂma M3āŽœ(JĄx:Õ>\Wq› ƒøqÖ„øģ19yėŅõYõ–ĩxo7.HÁn‡õ5’ô8ŅĪĩÁAŽâjøđlʗb§|#2-š*@oM†',ë°@_åõtHS¤tįé ArPKûÅ"ƒąŨÎi Ģ7Á”R™š>gã%Ä$ĩĨįũ`ąŗ‰œs„"Š]˜žŽîARĸKJŠxÂo.wōāXŠ”‡Ö'@„ĸEō—õ0'AŠ´Gē]ú˜Ø´9ģ×WQÅĄP,ũゆ÷zM_ŒB›ļ€/ķ›ü#ī^Ös¨õ°¤>—%üãDē ĶŪ=PQcęl0Tj›t;~0[•@5ĒĮAæaT WĸāãžrĪ‚ō×Ú˙9­¤á¤8Œ*„éÕøūpÂÚ+Ėī퇑Đĸ |[å+ĀmqÛļ _œf‰!íCŦ§rˇ~~qKîŅãÄĮIœKsķÆŒ=J\h^ΌiÜŌĨąGR•ΰ‘ũá<ËÁëŲ)äw0ÍCÖ÷üaIA팪 a€áÔķ†ģÃÎQ€t+ÎLÅÕSŒƒW‘č˜CĄH×ŊŦÆ"+YĨA}Āüáœĸ,)I?!ųÄ"Ģ­n•ũ8DWĄCÆÄÂ蛝BõĘü ŧ Ŗd€OsįpQ(?ˇļ¸Û¯ī 8`ŌßXÜ jx$@‰¤“{ŲŦcRuSqE4õ†á'°ËÕé–Ø(¯Dĸ+Žž Ņl:­/Ŧ0‡T•eJ:ÎÔÚ|ĀáĀ‚VũŪYĩ ¨ŽßŠ÷^$ ¤'‡[ë˛Ũ( ‚ŊwņœØ˜šPģāđČĄ)ōázwŒF—Đ*ÜŨQz{0"K.ôy&!ĸœž´ãīĢnŅ+rOAģ ÆT’žE)€3IõrČwØ*ëŪɍģ–Íθ…* b”‹Åä=掤7+˛xŠęcëtV)öå˛-AJœÄĀ@Œļˆæū0ėl„õYJ–”`Š<0(QmŅ˜‹”#m*ģęčplß$Š|b­ûYŲģ§Āx×M ŠbxĻ@‰P›ü­qĒk•hŊaœAxG]d“FO”ŗūķ‚0¤g+ÚâD Y'/Î’x5íÁōĸ)ÉxûÆMv-Ī3 #T”uPu‚hBR185(5<÷åČ÷é)A9'÷‡+ęŠØOīxĐj-QđyÉšKžąĄŨ5ũ°1|TĒM+ŠŽwŦ’pÜėQ?XÅ@é[ †9Õ¤Pdǁ÷ĀoŒ/ąTm+¤áÖnÍÜO8nQbčˇRėâĨƄh‘°ņúPönR´ëį<›f°ÜŅKҌb2i/ũëjˆy~Œ•Ė–i~یPL…×ë8ûæ–ŧĐåöøÂ2 w=^Üģ! ‚Ņ¯&tč’ECo+ČʁEŒ‹CKč×į.DwCŊŦ/‚ ŪÉËŒ>Ą)ĄĄē›į[Ã˛€U h=éÜÅĘ…UWaÆŧäBD6ÖGĀœ“BQ"úŲ“+•gÖ=­kf-kkĒáX,{˜Šb‘“<Ōē­nj`Ĩā‘đÎņTv­Į؉ÃD‡ÛŠģŋz?ŧ“š´Đ˜_•2x¤ mGø1•ކÁ:=j~pÂhTŲ§įh”GķŽĸĒ(ol‚DڀüEõ3IĶ Ō…ŗO;ąß/ä@žƒ[đL*ŨĻęŽ|ëéOĘžņށļdtOXϞĢǚkŪÜ~”SĶj`€0”ÁEŖ_œįAŒ,YâÕwŧm„ąŒŋ\+6XŲouœ Aõk^\¤ŠŦž‚_œXČĸ&ŽĪTgŦp&íĒ:˙æQ¤.žģĮ¸Õ Ō%×fe€­î]˙ÛÅ$$­+y ũbôhԟxĄŨ´(”Yāųr7¨€œ|L‡¸ŗD„ËTnâ‚īžąŗhj{Åt@€­ƒ_Ö_ębĖG…Ÿ—¤ v++Ũˆ–ƒYEt¤‘œyÆž ģßÃI;q€ÎˆVAøŪ5 ĒvĢĪXâļz=ųđw–8$Ąoŋü¨ĩIkmr‹–ˇhOyŠ$˜‚ųŪi‰8ČMčüæíĮ¤o˛ŦøÃ­ Ę[Ŋ„ÁTl‡;ëûČíV ¸Ĩ{ķ’P JÍũoÂCšįķüa):žųxÉÄč-hŠå›’M*éų1 CģãœB;T$ĮĮXö(Đ|äģk~WįËāžL (4âøįO Čc^TĨāŪ^Ųt´œEë vdéCßwY(*%Š$Ųëˆ;Gˇīį…[­ô`|`R.fšP?H ú‡LĀ-Ūž t„: yÉö`ģ(^LVhHŠų+„Š-ÄijSŽņN=RMĐÔ&-šĩHē؁XQjFĀzgŧ›áX6ψj^Üa@U7Ŋ|Ūp&D͏Īá’ŠÎōĨĐ}aƁäšSÎWvöc^ čœü=a8[Įöį„!8+öë(Ē•Ę:G#4”Õ˛Œ'ô€´N–ũ8ĄŦJP,w ¯XĘ @;Hōƒŋz1 āŅ5 ¨ šë‚‡kû/įi'UŗīZ>„áĩîLE!āUÕôņŧ[1% Ķä]<&š1%öar;^‰8ÎBƒdDõ “ˆå{m´V( 8+ˇFąž„,{7ÆNØ#ájWįXU¨!­ÉĮÆ>Ũ„’SÄl 2A6ąō^2L%Š-UôīŒĸBŠ4žé:pĀ\ Єâc8XØ ķ¤zOë,ë`u°Ûrčō.1*„#_æáĄŽČ‘tíW[ĀFļčŅmŠŦe!“J ÁĶ4“<™?x ;!EĶd|õėÃhYû<äô:į!u}o€hyjÍūķZā¯7ÎŋŒ&&âlˆŋtÖ¨Âw÷=ãŪ)A}Éøë9QQ@Í´EqžIč– Øōä_Ã(ˆ4-@Öx=ūĘ"#A7Īøč°›ÄŦH”(„DKGjĨ[Æ,yDˇ¤Åî( ˇBŦĮQ %ŗm.á‰æQU^dPˆÆv`ō˜J‰ob;ĀH šy›¸šôß_ ãõvxĨJR&‘gœ^bąēe" "^b9eSG;<ūū<栄`1ˆVŌÁÛŠŦyõ*،¤Õ ˛œcGpœļda8& *ĩJe×x„8˛Hjošé˙ķŧēčŠČ+ˀÚ¨”Či.Ģ<€R—’‹Üé„@ĻhnĒsūW’ ž×€ąëX+&鐁‚Ätw8e hŠBsRē1ÍU jû¤§ŗxtĘ­„ŽĀ<§Bc ėāt(ŒA;ËVž‹I‹ JÃs8(vŦ[äÃāp†ŠĶG+;HWv`ūĘ Ÿ|#Â*ÂZŽTs§¸@\d ˜:wūŌgJ°Ue@ģŨ,Ē;ĸĸ†Íŋ\+&2DԊ‰;[ēÅŧębÃÜņ×ō™t\ëļ :4^"o*P%4œīÎ(É#ÄēøÃĒ‘7zËúfLwoō`ƒž,:zí=˜E ‘Ŋāt+ÁßZČ`ČHí,÷ˇ ëŽĐPˇG[ÉoĄ´ā‰ ZS~põäėABė4i#slTĀ(Ē4-°ÚĖ~§Ĩ+Šw<âęÛĄŗpQ–N°ĀW¤îk#´-ÖĨf.ËJ:vb)+fČ2ŨhŌëĻâī7Đ*?F D`••'*ĒŊĒäŠč†5­(ŌkMfBXž…‚ōU:ēcãĀŦÃÚ Ã]J-ĨhÃIŖŒ<ą&JÃĘĒ㞁ž—?Ÿđ.5ŦNÄĒ’ÅĨŗŠ P:Dá4Ķ1XÁäáŅšPGáé`éŖÖ0ēqĻC”TŌ„=cüoM8HĶU¤Į@7Đh´č097dU°÷}¸zâ ¤pƒßÄėBŨ×]z$7d+‚9†/’øĨėŽ†6\”/˜ņĐË$(Ø(Bē§8’DuR"<‰† )gmvē_wœI0†Ž‘ŪŗŽ2™(›‚Á,ÜŦåQԛ|ÅÎī%/ ĨW⠜ŦPĶ4ƒlHB†iqD”PŽ­rÛĐĸx(‹Ė' D–Š$ŲŽT Z=„T؎“;ĩ|ÔĄRĸŌĖBåDžŒĐ+$ÕĮŌ‹d:ėžQŅČø—.=ÔŠÍ4iĻ/Q˛6lđūqÁÍRĶ}ķ1Š •Ä’įsÎ:‰2šÖüëŒĒjˆ冯ˆ“ĮœĸčĄ<ũķŠ(.ĶÃN4oXS‚ĩ-Ū°‰p*@WH0c7†HLqŊœ†ÔøžûpWnąl" *sė/ßX*Iš‚Ŗ#ÄŲõŸŗ˙ LTØ­˙Oûžy ģŖū8r{؀ĸ:*Ëm"'߲Ü^l= 'šī œ=Ûŋâũæ:ĶfOũÄÇ/÷΃uų-`(´ö Á‚pPļOĸ"‰č6]Šéž‹Ö9sŨė@øĪÛ˙~ß?úžūw—] 9cÕ˙VÍđ˙; IĮ˙MĒ&­WĮĀãåũ˙÷ü˙):?üËģBV'đ—â!¸@T(/Ÿœ<Š(äī“eÂļ_ką)V •;ĘčpheØĸR…?üRõ.§r-+ĄŽÚs,낄 ° xEOA˛ėWHô^ą^õ´{ąāüR˙ģį’‚ ?ņÓŪÄŅP>[iA8~˙ūoęl(“.h­]Ū ˇūRĨ[HC´CālōZNCƘ÷“ē†ŨmĮr2’P˛°Ôį ö,€NBs…ôĻėë—Î!HNx?wŒtčQV$÷šä“c¤FÎrÁR՞u׌ßPŅA söfäH ʓyp^wc-ŧCĮgOŸyAAlwÆąqUĄäûR €`ë­¸üҰÚBüÖü\yŅhŽŗöū‘t˙ģį…AaĐ ŋâšDémĄĢŘu›–ƒF0ĮΙĶ6ôÃ:áĖĀ Ļ=*~ž4H÷ö_X\ ąÃ5ø9Hf ÕĢt8×8KOĖb˜Q€Z&Ē;‰M|9аjpō¯ŧũŋđįíķ˙Ģį˙įyuŅ$”‰ güß힍čßŋÉūSj#ācü™ÃB”ØU>,˙˙īų˙ŲŋŸÃūWwvo„Īá˙1+0F¯UCīîLôĸhéFxšÍAúx|"A=cFPu¯„XÅh c၅wGĒøĨëc~áFZŧ!&°–Ÿ˜Å0ĸ´MTv#šøq[Ų°IāyWŪeQˇ‹?îųáØ\t¯øĻ‘:[hjņffå ŅŒ1ķ‚Xå ü#÷Œ¸(ĩœūņ"Lú UŨZā°’ŧ0*L6Ģ˟ˇū­ZšuĨâŊ8Ír‚žÃˆB…TōsķŦ !PEou=üā Á•Ø$°î8O@Ņ 9O?ų•ē māęd°‘˛'kųŅëÔą,hĒ=ÁØHü ĮŗFõ?xäŦķyÁp1žPŋY00 ÂúrĮƒZ]r|šPëyVßۀ!8ŌŠÕ;’Īxú‚‚˛yœeB“rJ˙XKĨ€j>Įâ§Gč0["xķŦj**ņŽķjžĄ…ßXA<ęŋ •XÉë5j¨ĶB<ũ”ŠRŠ‘<ōã¤ÕjÂ-oââ ÁfÔŒđīx†$Ĩ"oĖüáM”Ĩbũmúʈ2(č^YŽ´ųo:ãBI•ŦŖ€’â %%ųį‰%Ũ‹xđëyđMU} úĀøßĘEj˛‹ÖĻ%˛n‡Ĩ ŽË%•Ú¸š (0§“õ†ĸļ‹oFAM ]ōa†’€˜WR7X´ZSj!ør6ę }0Ŧ ĸlČ ÎˆÂä+R[Ą¸øië Ø˛!čPXOČĘæpĢđ›ü`ą%Ÿ{hÆŪÄo.÷ƒ„—Ø/āÄã@€ ÔĻŪî0ŧAЧŸ l 7{ØŒ!DФ~ÔM >Pk ĀķĄ÷Œ_(ÄŅ䚘úe ¸ 'ÅqŦģu œr_ŧĻąÁÅDā‰A'2ģÛšŦۚĐô"ŋ7įŧ‰˙åeiķ‡Ģz‹Û|uÔ0Ų8ø¤´]ĸv)Žr(ĨfĐuX2á7KŋI–" 9Ų†€)›ÔXíĩnŒņ@“öp_0 ŨڟŒ v ¨*mx6ßyƆŒ¯¤tų1Ņ€ÛâĒ|Ė邈ōMcx ŦŠ|¸= Î&…PÕOˌ‹"DFڏ_6’ŊOˊb>”Ģ×xõ؂ŪÜTh6P°Žˇm׃č`{D~ŗXÖÆ‘Ķĩ~“ŪˇÅ4>‚"ŧkQ…F…K§‘ŒBĶžšŅųr¯;ëëĐC]Ģ~Ļ7^å•Ĩô›ņƒ¯ŠÄ%ŲBøZšG+¨ČښGĄžrfĐå=€~˛†|U/ž&9āAÍM2Ā"qŦ­,ƒŧ„^N1ˆ  ǧ'ž6"bŠÁ‘Á„C‡Y{ûʡ…üđaC wõÁ!]JįœÔ@0JƒŲ2—9MPãá;n_qCm*@Ö+ˆ„u;—īPĄ×ŧDe>9įœ;mY(ÍJâŦÕZ|˙ŦBŠčw§ YĒJƕí7=3€5)gŲúĒO2“æãjŠė×Ρ–ˆH‡bĨ=.8€…H…ú˜ÖĘ Ã’ŋEÍķ:Ķüūp‡‡™hīų˜Db:MūŨâˇ8*|‚§úÉPAH7šĀ8¸—A<œD”U vŠ^r)–čL×ÖLâCÄúĮ§g ×ÁŒėĄō=ŽNņōˇ5B‘Íë“ãũæœZ čį ˆŋ.JŖŒ”|Š×ãĒÚtŽØs;_HÕDkKoë`Œo‰į ā6ŗ{ËųH ߌq]QXĄčÃú ^L~°Ā`įƒX¯ Éãī åŠ{swĄUÕ9VVLm* õ† PIhBûČēyš Ķ-Pkx>LoĐrĩ4P/Ū tbĄ_“įŦđÚÁPGŌ/Æ[´ŊJĨōáuVÄn­_9x9/4Đ^>ōũĄ*ōŧdH)/vuũģÖĐ[ŗŽĘŸŧ{RuĮëocŊܐUƒŠ­dåFÁsÃÎīŒa°QœP°j ĩĨíČĐFëœ:ŠhnēÎ9€WaęyÖUFƒcy=›Vux¯æũbhV6ͅŅãšÖ¤øÂf%pj}3éžqΰˆÂö Ũ@{û2G(ƒ°^ųÁ㎒“áq¨ŠĒĻÃėL"čĮ*čßįāĸ Õ}ãš#…sŪZp rĩĸŨā'Ą"G‘?Œue%U[Šüc(Z"tQ÷0VBô^„СvąIzÎđ–Ę׋ĪŋS ­‡¯ž˛=nŋ>q NÂīx׆$ߟëh–Ļß_ŧA ‡NųÆĸ įÆ^Á ­íņ’ -‘ūpĂĄ]Ķ•Á‹D^ĩšĩ˛‡ķ&"tRÃkũbV$ ûg!äpĖ…%āÖ°FRäBOfâ€wĘk%Ú2ώŽ2Ô +Å|ōŽPph×( ģ×Öo&yVŦ¤ŠŧŽ[(vĘ8ŸLA ŠĻMcčQ Zŧ¸€6"]„ãÖ%Ē/3ģ‚ą%hCķ’ąCģŽq¨ųŧ\Ü[TīŠårP•hč3x‚’>…?.tΑēvĐŋœßÎKØ_YgĮ˛ũÁ„ ÃĢũãŌ­"^æ ‹•æ6ŋŒrfĖõu„ęAäN=_8tŽIRhWąJ™ĀÍ"ČŖ‚ä)`‘×ÛÆ$ØÆŠ9hK•Õ˜$Uxķ ĘO8|į’/]ãEmé™8(Í%Ū=iVj˙ž°Á D4öGĖī,6RX÷Ū j*wŋŒŲ˛žÍyž~0¨ā–0Aˆí5ûęRŠ6^8ëŒ%Ņęu„5 Ã$7ž°’V !āk€ VÂoė@ļ§o:@U‡Ŧb16ü>Nˍ,ąÎMĶįxPSœĸ&9Nô†‘é˙xiF09uü†… tړĶũCT‘*EãįRæd¤CáėĮ Hš1¸Ņšíšū°Ô^âoĐ:9uūßō9tr<ģrF­‚Øpwve>…ԀŠĐ'V—Ŧ•ÃEU î¤ķXJČD'ˋ"táqoę@‚čŽÕQ"›’U.â@ĸØPŦ…ÁTaocĐÛßOx愿P4ŠŗBDĸ-õzÁvĢĮß4œ%×3  b” ãąŨW‚å ĄK KĶ6¨ĄqæoI-0 u‚…dU¸ãŠȔCÃŠČąúéNfČF ¤Z"ŽûÆ's"$ŦáˇTŗE˜_Äųjž'yM4•dbĐAŨ ”Å’­‡Ā_ŦØ0\æQĐģy†.„•ŋ=ą Â{ũEJW™–Oŧ:Ę›ŧÔÛ;}ˇ:{Á(A¨X°Ŧ¤P”}”}‰•­Lé´č/<ŧ ÖęĄzŠ„R""•€, MßsĖÆCčŦĮ7ąH ē9M’(:æ°XĨx: Ex ~PÅņHäkK†ÁFP¸ĐϝŔëŸ8ĸFD š2xÖbH‹O_j ÜN.3ÖKÖÂĶDŋYßø#zũdāfĖŒ]īã ÁՅZĒT‹Íwõ‚IâäėĘq1 #P´ũ˜%Š*¯ž.ķdCcŽšhŌâ účķ’đN ¤<äĪ ƒÁÉ0 DDĸ=WNˇŠČŖČ  ī{Ž QV•īŽ2Ū„Ŗ6gXāЈ€M fČJ+Úŋ¤ė&÷ڊö̃†ķh/€éĀĢ•ĮS)ĩ” Ĉ… øÂ‘CȊdLuĸˇÛGŲ›}ŠĻČGIi%ÅÖĮô–ÕtqE`Øũ2Ŋ*šo„xzīPãŋ/mp "a”4ĸÅ4ÄĨXâ°Ø$=0Gȧp’æ"FķâŌU¨šį%´jŠŧU›jŋ3Ŧ—y\įŪ>5“ŒÛēã!@ÆûĀ–+UíČĨEu‚7˛ŸœŧCąeßX2ņ$žT&2Š’KŪ¨¤ō\× ûÖm’h’u“mļF÷Ū;ˆ pû¯8ƙ;@{ 닆ļ"īSJęŪšžņ Ā ’Đ>NIī %H7¯‚kÖ âļ¤# <ÉķŧpD¨ÔŠ^xqb*ižĩÕ×ŌŋÖDčw5ŦģH@toY|€B'†‡@Í!Wƒ_ŋüĮË "Ņ>1˜(ŲLÔĀ |ÎąpåRqī•‘ÂŦ¤AųŪYa #Čø$ûÉ8AĻ•ru ßŧEÉv2ÆnyY @i ËīœiTâ÷´˙#—qŠ •÷ĪąÔXĀÕĐ۝ŧė1üô…x‰Rĸĸ!ËGĀ?0ŠË>#ũ?æ/ûž?á1#Â_ŦŪ‘B¨qA¤b1#ŧv´+°Ŋ[ #-s˛‚ĸ’ŠíqŧA…‹ô/õ›‡įũ˙™‹˙Ųņ˙4˙īxgü˙÷ëך70ŗaå€ °Å⚠áĸiđA4UTYZ¯ũ_5øžĶ‘DDģ=‡ķžTDKĒ=a…˙+ū9ģxÆēAĸƒ5ÆÚIžĨūÕâ]˜ŽÖ“ĨvĢb¤cEŽvPTRQ]Ž7ˆ0ą~…ūŋÉqĀ‚įŒ¯Į‰6ZˇŸxzˆ8˜P.Jėŧ¸´”vūrz'Û‡ hžq’˰;/gŧ›ļ ņędįBĀķÔĮëäŖ„ŅKÅëĖ1f|KBŠƒo‹ŒĶĄ+6œĖz"bX]Uu{ø¸‚‰° ‡ãBĸ<ë˛f›Â +ŦĪ&WE§““ÖˇõƎEųŪ>jvG „„§—°ôãˆ5hŠ}õÆ[)7k9ũãõiī"Ļtđī ļ=—ĪŪú:@ÛÖ1ĩ^Ü&5^ņ&•]åp€CxG˙på”;0H˛é´e/Øy< ĄĐ<ī,ÉŪ1â*–{ÃC€īÖ9„ÂWuBē‹?Čåæ(0:æáéÉ[Ā´öâÅÜJŲeĸ1w•NßĮ˙Áčŋîø˙„˓ŧčDSÂāë4¯]hpVĢĘģ_đ™  ōæ˙™‹˙Ųņ˙4Ä&üpĩū0ˆ+ŧ #ŒhNרv`%áđ˙‚˛č/į'Xûf}"}`Ųßá‡H”%˙ĀĨĄĐ s…ÅîøÉö?_ái`iōSĪÍIâv!œ=f•ãk­ ÕyWkū!¨/įūķĀÎW¤%!‘NJbj6.u" Ē>5J:.ĀĘVœJZ"uī …¯Ëâ)//X‚ŽžŽ ķHĮëB#]—ŒĖœ#CPNî9ï ĘÅftų0$C~sx+hčķ¯ī‚ ĐCįAb‡5|á#iá~dĮæ7/#ßũķ–ŧEŖ™ü}á7w_mãĘ8aãÆ)a ŋ&ėûįē‡x=hãwW.‘¤EŪōXŗzÕō`­— ‚sķ1‡oS&M!ɃÉ@Đ˙īĸĀ‘`ãŸ8V@s4ąOÎsá ŋy*#(9O8ĖƒŪ Ņb§L&‰`íŧjŲnĻ)Dύg Ũ2ĸĀVāĀw#ũn#§cFXËȄj ‚iĀS"!Ļ‚(ĩ%Î ĸ‰ˆëƒŒOé´ˇ„$[ŗ[?Ãũ„{°”JjޝLTsnĩŒ–Ŧ¨QHâA—ĩzZ Fs:+ ŗrĄ~<äļĢÜĮ`Eå#įîÆėhV%EƒČ2Z˛#UPUa—yB&ÚTĮy ‚PģLá€ÛĻTX Ã@ŧV“ȁDypX׏1@ *ŠĄx?ÃôLŋ= xŠÍÄE(M‚Žhžĸ<”Ū \ājB ÕJa}V„¤4¨°ĢÁūĩÃ}("lzÆ>ë° ­D(­# b‚aāØ!äōy{xÄŽ  đkī:Ģû€hú•J=‚”›HĖNAPT Zb>((cu^ĩWē­ĒUÚ✿vԚ‚ˆ  W%ĩ^æ;/)8Įv7cBą*(@1’Õ‘Ē€Ģ ;ØŅĀŠč)ā"GN% ĐáTSĻcH,€j1 ­q†0•BĘTE-{Äíڜ4ąĐäËĀĩė@ˆ:ĮZ(M7¤ŒB"ŦÅŽšP6ĸx†œ‹P{Âd€C(^6žY#„{Aãs+ÜÅ.Āh6üã4vÜįŽÜUĮ‚6CŸī"V‘mMŽÂ'°B—­ėÃ-Ęęu8úÆHOĻŖrŊĪ]E>ąØ˙YD*­W•>ą‚ Éōä˛ÕĀu‚Jˆ›^pĄ6Ö3âGĮœ2h Ō÷–ÔwŅëĻ †Î.āZoEÉ@*īœYx ÎAUa,ŸŦp% ›‡§ÖSRHGÕęãÉ ĀBøRįÂd 킆Ũŋ.ķjBŪ))éĻŨ`_e¤„Ŋ¸mõÉ@|eˆ œtôqũ9¸FjW˜§+_ę`9tŖË ûÅāī1žŠ˙xÎkëÖJ™F_9ÄDlyøË††ŧéõ18 ōĀd"Ũ#˛Í}EĒöĢËrkßBÍø×Ŧ´Œi =ÂūĖŲp=ZüŽ$ŖlfäŸČãq-ĢĶĢŽ0 ēoŒĸč¤X߃2CĄ#äûĀ™K$Ū@’w'?. *•Į:蹝5t`=  â éÎÃFŖōŸŪUŌFĮ¨@žæ„‰į ’Ō‚ėDÆę2„øD>ƒ€6L×Zīĸ„´Ņæöļáj›Čõ0wvĶ5Ę<đųŠT钇ŌÂjĒh•ZŒā†ŗ@Rė”;&X.Qņ‡<öâNdŽ˜Yw1VgG™€Ÿ”{šT1{s—D(ŧûĀ…ĩAOģąôŽHJĩķ’Á(GtŲ’f„— ŨhÍ,ņŽ´ˆdÚ*n9Ģ |Ü6Ŋčâ‚Ęũa:Đūf2ÍĘ*TūŒK Ũ´(øŗXA@ 8ßwûČĘ ”ËāŧŽ€F^đR{0=aņ.;MđphÛĖzøČ™Ĩã g%'X.NĄīŗn #ˇ}āęŧ^wš8ÉŲ›eDwˆä”jämšb!ËŧĐBōčzųÍåhkNōëĶÖ$S|fÄ;. ‰n$Ĩ̝ŦÎi |˛ož`Eh Î5[OcĻđ Ô>p“cAņYũåÅ@cœ@ģsqž<Ŗ™„18žŦŪD`^ã ȑ2ž1^QmęäÎė2ĸį,åČ"Tķ‰w€ d-°ptĨõ‡Ú‚ŊÜČ_ox‡ŒéņˆŋÃW"zČĄ˛¯Ū7BĒáŨ3˛ ‹8 û>pÖQŅŠ”ŗXŽKõX¯j—“œĄõfņ`#Iåį Ž´˜˜˛™{–gdËĨ­ĪFn›AKˆËBĒš3ŧy`ŧeÜb‘Šô:1ȑĩ9Ęf)ߗƒ9ƒ #­õ†@C|ŋĒ\KuS\I?9æ/_ûŸ˙Ųmerecat-2.31+git20220513+ds/www/index.html000066400000000000000000000045411424066547300177170ustar00rootroot00000000000000 Welcome to the Merecat embedded web server

Welcome to the Merecat web server. This is the default landing page for new installations. Seeing this means the web server is working and the owner has not set up any pages of their own yet.

Merecat started out as a pun at Mongoose, but is now useful for actual web serving purposes. However, it is not a real Meerkat, merely another copycat, forked from thttpd.

It expands on the features originally offered by thttpd, e.g. native HTTPS support, PHP CGI, gzip deflate using zlib, dual server support; both HTTP & HTTPS from one process, and built-in HTTP redirect. The resulting footprint (~140 kiB) makes it suitable for small and embedded systems. See these fine manual pages for more information:

Credits

Jef Poskanzer for creating and maintaining thttpd for so long, and SunShot for the Mere Cat meme, huge props!

merecat-2.31+git20220513+ds/www/main.css000066400000000000000000000060511424066547300173560ustar00rootroot00000000000000body { background-color: var(--body-bg); color: var(--body-color); font-family: sans-serif; font-size: 110%; /* flexible baseline */ line-height: 1.2; text-align: center; /*For IE6 Shenanigans*/ } h1 { font-size: 1.7em; font-weight: normal; border-bottom: 1px solid var(--line-color); } h2 { border-bottom: 1px solid var(--line-color); font-weight: normal; } a { text-decoration: none; color: var(--link-color); } a:hover { text-decoration: underline; filter: brightness(80%); } p { text-align: justify; } pre { font-family: monospace; font-size: 0.9em; } address { border-top: 1px solid var(--line-color); margin-top: 1em; color: var(--desc-color); } img { display: block; margin-left: auto; margin-right: auto; border-radius: 10px; border: 1px solid var(--desc-color); } dl.config dt { font-family: monospace; } dl.config dd { text-align: justify; } #header { text-align: center; } #footer { text-align: center; clear: both; } /* Man pages */ table.head, table.foot { width: 100%; } table.foot { margin: 2ex 0ex; } td.head-rtitle, td.foot-os { text-align: right; } td.head-vol { text-align: center; } .Nm > tbody > tr > td { vertical-align: text-top; } div.Pp { margin: 1ex 0ex; } div.Nd, div.Bf, div.Op { display: inline; } span.Pa, span.Ad { font-style: italic; } span.Ms { font-weight: bold; } dl.Bl-diag > dt { font-weight: bold; } code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn, code.Cd { font-weight: bold; font-family: inherit; } @media screen and (max-width: 1023px) { #wrapper { width: 80%; text-align: left; padding: 0.5em; margin: 0.5em auto; border-radius: 10px; border: 1px solid var(--wrapper-border); background: var(--wrapper-bg); box-shadow: 1px 1px 10px var(--wrapper-shadow); } #content { } #right { float: right; width: 75%; } #left { float: left; width: 75%; } } @media screen and (min-width: 1024px) { #wrapper { width: 800px; text-align: left; position: absolute; top: 0; left: 0; right: 0; padding: 1.5em; margin-top: 20px; margin-bottom:30px; margin-right: auto; margin-left: auto; border-radius: 10px; border: 1px solid var(--wrapper-border); background: var(--wrapper-bg); box-shadow: 1px 1px 10px var(--wrapper-shadow); } #right { float: right; width: 63%; } #left { float: left; width: 63%; } } :root { --body-bg: #f2f1f0; --body-color: #555; --wrapper-bg: white; --wrapper-border: #c8c5c2; --wrapper-shadow: #BBB; --desc-color: #c8c5c2; --line-color: #f2f1f0; --link-color: darkslategray; --link-color-hover: #000; } @media (prefers-color-scheme: dark) { :root { --body-bg: #212529; --body-color: #9ca59d; --wrapper-bg: rgba(0, 0, 0, 0.7); --wrapper-border: #252525; --wrapper-shadow: #252525; --desc-color: #9ca59d; --line-color: #9ca59d; --link-color: #dee2e6; --link-color-hover: #fff; } } merecat-2.31+git20220513+ds/www/merecat.8.html000066400000000000000000001130341424066547300203740ustar00rootroot00000000000000 merecat.8
MERECAT(8) System Manager's Manual (smm) MERECAT(8)

merecat
Simple, small and fast HTTP server

merecat [
-ghnrsSvV
] [
-c CGI
] [
-d PATH
] [
-f FILE
] [
-I IDENT
] [
-l LEVEL
] [
-p PORT
] [
-P PIDFN
] [
-t FILE
] [
-u USER
] [
WEBDIR
] [
HOSTNAME
]

merecat started out as a pun at Mongoose, which is another great web server, but is now useful for actual web serving purposes. It is however not a real Meerkat, merely yet another copycat, forked from the great thttpd created by Jef Poskanzer.
The limited feature set makes Merecat very quick:
  • Virtual hosts
  • URL-traffic-based throttling
  • CGI/1.1
  • HTTP/1.1 Keep-alive
  • Built-in gzip deflate using zlib
  • HTTPS support using OpenSSL/LibreSSL
  • Dual server support, both HTTP/HTTPS from one process
  • HTTP redirect support, per server. E.g., possible to redirect from HTTP to HTTPS. Limited set of Nginx style environment variables supported.
  • Native PHP support, using php-cgi, if enabled in merecat.conf(5)
The resulting footprint (~140 kiB) makes Merecat suitable for small and embedded systems.

This program follows the usual UNIX command line syntax. Some options are, however, not available when merecat is built with support for /etc/merecat.conf. The distributed archive comes with an example configuration file, which should be fairly straightforward to comprehend. For details on the available configuration directives, see merecat.conf(5).
The options, in their entirety, are as follows:
CGI
Wildcard pattern for CGI programs. The config file setting for this flag is cgi-pattern = PATTERN. The default is “**.cgi|/cgi-bin/*”.
For more details, see below.
PATH
Directory to chdir() to after chrooting. If you are not chrooting use the WEBDIR to do a single chdir(). If you are chrooting, this lets you put the web files in a subdirectory of the chroot tree, instead of in the top level mixed in with the chroot files. The config file setting for this flag is data-directory = DIR.
FILE
The config file to read. By default merecat looks for /etc/merecat.conf, unless the software has been configured to use a different prefix.
Use global .htpasswd and .htaccess files. This means that every file in the entire document tree is protected by a single .htpasswd or .htaccess file at the top of the tree. Otherwise the semantics of the .htpasswd and .htaccess files are the same. If this option is set but there is no .htpasswd or .htaccess files in the top-level directory, then merecat proceeds as if the option was not set — first looking for local .htpasswd and .htaccess files, and if they do not exist either then serving the requested file without any password or access restriction.
The config file setting for this flag is global-passwd = <true | false>.
IDENT
The syslog(3) identity to use for all log messages. Useful when running multiple servers. Defaults to use the program name, i.e. “merecat”.
LEVEL
Set log level: none, err, info, notice, debug
Runs merecat in the foreground like a regular program. Required when when running in a process monitor like Finit or systemd. This also enables logging of errors and warnings to stderr, which can be disabled with -s.
PORT
Alternate TCP port number to listen on. The default is 80. The config file setting for this flag is port = PORT.
PIDFN
Optional PID file name. By default the IDENT option, or its default value, is used to construct the PID file name. Usually this results in /var/run/merecat.pid. If the argument to this option is an absolute path it will be used as-is. Otherwise the argument will be used as the basename for the PID file.
Do a chroot() at initialization time, restricting file access to the program's current directory. The config file setting for this flag is chroot = <true | false>.
Use syslog, even though running in foreground, -n. merecat uses syslog by default, this option is only relevant when running in the foreground to prevent warning and error messages to be printed to stderr.
Do explicit symbolic link checking. Normally, merecat does not expand any symbolic links in filenames. For increased security this option can be enabled to check that the resulting path stays within the original document tree. Note, that if you are using the chroot option, the symlink checking is unnecessary and is turned off, so the safe way to save those CPU cycles is to use chroot. The config file setting for this is check-symlinks = <true | false>.
FILE
Enable throttling using this file with throttle settings. See below for details.
USERNAME
User to drop privileges to to after initialization when started as root. The default is nobody, on some systems www-data is preferred. The config file setting for this flag is username = USER.
Do el-cheapo virtual hosting. The config file setting for this flag is virtual-host = <true | false>.
Shows the current version info.
WEBDIR
This optional argument is provided as a convenience — by default merecat serves files from the current directory. The config file setting for this is directory = DIR.
HOSTNAME
A second optional command line argument can be given to specify the hostname to bind to, for multihoming. The default is to bind to all hostnames supported on the local machine. See below for details. The config file setting for this flag is hostname = HOSTNAME.

chroot() is a system call that restricts the program's view of the filesystem to the current directory and directories below it. It becomes impossible for remote users to access any file outside of the initial directory. The restriction is inherited by child processes, so CGI programs get it too. This is a very strong security measure, and is recommended. The only downside is that only root can call chroot(), so this means the program must be started as root. However, the last thing it does during initialization is to give up root access by becoming another user, so this is safe.
The program can also be compile-time configured to always do a chroot(), without needing the -r flag.
Note that with some other web servers, such as NCSA httpd, setting up a directory tree for use with chroot() is complicated, involving creating a bunch of special directories and copying in various files. With merecat it's a lot easier, all you have to do is make sure any shells, utilities, and config files used by your CGI programs and scripts are available. If you have CGI disabled, or if you make a policy that all CGI programs must be written in a compiled language such as C and statically linked, then you probably don't have to do any setup at all.
However, one thing you should do is tell syslogd about the chroot tree, so that merecat can still generate syslog messages. Check your system's syslogd man page for how to do this. In FreeBSD you would put something like this in /etc/rc.conf:

    syslogd_flags="-l /usr/local/www/data/dev/log" 

Substitute in your own chroot tree's pathname, of course. Don't worry about creating the log socket, syslogd wants to do that itself. (You may need to create the dev directory.) In Linux the flag is -a instead of -l, and there may be other differences.

Merecat httpd supports the CGI 1.1 spec., https://tools.ietf.org/html/rfc3875.
In order for a CGI program to be allowed to run, its name must match the pattern specified either at compile time, on the command line, or in the config file. This is a simple shell-style filename pattern. Use * to match any string not including a slash, or ** to match any string including slashes, or ? to match any single character. Multiple patterns separated by | can also be used. The patterns get checked against the filename part of the incoming URL. Remember to quote any wildcard characters so that the shell doesn't mess with them.
Restricting CGI programs to a single directory lets the site admin review them for security holes, and is strongly recommended. If there are individual users that you trust, you can enable their directories too using the pipe syntax, e.g. "|/jef/**".
To disable CGI as a security measure, either disable the default CGI_PATTERN in merecat.h, or set the configuration file option to the empty string, like this: cgi-pattern = “”
Note: the current working directory when a CGI program gets run is the directory that the CGI program lives in. This isn't in the CGI 1.1 spec, but it's what most other HTTP servers do.
Relevant merecat.h defines: CGI_PATTERN, CGI_TIMELIMIT, CGI_NICE, CGI_PATH, CGI_LD_LIBRARY_PATH, CGIBINDIR.

Basic authentication is available as an option at compile time. See the included configure script for details. When enabled, it uses a password file in the directory to be protected, called .htpasswd by default. This file is formatted as the familiar colon-separated username/encrypted-password pair, records delimited by newlines. The utility program htpasswd(1) is included to help create and modify .htpasswd files.
merecat can use a global .htpasswd file if started with the -g switch, or you can rely on a per directory file which also protects subdirectories.
Relevant merecat.h define: AUTH_FILE

Access restriction is available as an option at compile time. If enabled, it uses an access file in the directory to be protected, called .htaccess by default. This file consists of a rule and a host address or a network range per line. Valid rules are:
The following host address or network range is allowed to access the requested directory and its files.
The following host address or network range is not allowed to access the requested directory and its files.
There are three ways to specify a valid host address or network range:
,
e.g. 10.2.3.4
,
e.g. 10.0.0.0/255.255.0.0
,
e.g. 10.0.0.0/16
merecat can use a global .htaccess file if started with the -g switch, or you can rely on a per directory file which also protects subdirectories.
Note that rules are processed in the same order as they are listed in the access file and that the first rule which matches the client's address is applied (there is no order clause).
So if there is no allow from 0.0.0.0/0 at the end of the file the default action is to deny access.
Relevant merecat.h define: ACCESS_FILE

The throttle file lets you set maximum byte rates on URLs or URL groups. You can optionally set a minimum rate too. The format of the throttle file is very simple. A # starts a comment, and the rest of the line is ignored. Blank lines are ignored. The rest of the lines should consist of a pattern, whitespace, and a number. The pattern is a simple shell-style filename pattern, using ?/**/*, or multiple such patterns separated by |.
The numbers in the file are byte rates, specified in units of bytes per second. For comparison, a v.90 modem gives about 5000 B/s depending on compression, a double-B-channel ISDN line about 12800 B/s, and a T1 line is about 150000 B/s. If you want to set a minimum rate as well, use number-number.
Example:
  # throttle file for www.acme.com 
 
  **              2000-100000  # limit total web usage to 2/3 of our T1, 
                               # but never go below 2000 B/s 
  **.jpg|**.gif   50000   # limit images to 1/3 of our T1 
  **.mpg          20000   # and movies to even less 
  jef/**          20000   # jef's pages are too popular
Throttling is implemented by checking each incoming URL filename against all of the patterns in the throttle file. The server accumulates statistics on how much bandwidth each pattern has accounted for recently (via a rolling average). If a URL matches a pattern that has been exceeding its specified limit, then the data returned is actually slowed down, with pauses between each block. If that's not possible (e.g. for CGI programs) or if the bandwidth has gotten way larger than the limit, then the server returns a special code saying “try again later”.
The minimum rates are implemented similarly. If too many people are trying to fetch something at the same time, throttling may slow down each connection so much that it's not really useable. Furthermore, all those slow connections clog up the server, using up file handles and connection slots. Setting a minimum rate says that past a certain point you should not even bother — the server returns the “try again later” code and the connection is not even started.
There is no provision for setting a maximum connections/second throttle, because throttling a request uses as much CPU as handling it, so there would be no point. There is also no provision for throttling the number of simultaneous connections on a per-URL basis. However you can control the overall number of connections for the whole server very simply, by setting the operating system's per-process file descriptor limit before starting merecat. Be sure to set the hard limit, not the soft limit.

Multihoming means using one machine to serve multiple hostnames. For instance, if you're an internet provider and you want to let all of your customers have customized web addresses, you might have www.joe.acme.com, www.jane.acme.com, and your own www.acme.com, all running on the same physical hardware. This feature is also known as virtual hosts. There are three steps to setting this up.
One, make DNS entries for all of the hostnames. The current way to do this, allowed by HTTP/1.1, is to use CNAME aliases, like so:
  www.acme.com IN A 192.100.66.1 
  www.joe.acme.com IN CNAME www.acme.com 
  www.jane.acme.com IN CNAME www.acme.com
However, this is incompatible with older HTTP/1.0 browsers. If you want to stay compatible, there is a different way - use A records instead, each with a different IP address, like so:
  www.acme.com IN A 192.100.66.1 
  www.joe.acme.com IN A 192.100.66.200 
  www.jane.acme.com IN A 192.100.66.201
This is bad because it uses extra IP addresses, a somewhat scarce resource. But if you want people with older browsers to be able to visit your sites, you still have to do it this way.
Step two. If you're using the modern CNAME method of multihoming, then you can skip this step. Otherwise, using the older multiple-IP-address method you must set up IP aliases or multiple interfaces for the extra addresses. You can use ifconfig(8)'s alias command to tell the machine to answer to all of the different IP addresses. Example:
  ifconfig le0 www.acme.com 
  ifconfig le0 www.joe.acme.com alias 
  ifconfig le0 www.jane.acme.com alias
If your OS's version of ifconfig doesn't have an alias command, you're probably out of luck (but see http://www.acme.com/software/thttpd/notes.html for more info).
Third and last, you must set up merecat to handle the multiple hosts. The easiest way is with the -v flag. This works with either CNAME multihosting or multiple-IP multihosting. What it does is send each incoming request to a subdirectory based on the hostname it's intended for. All you have to do in order to set things up is to create those subdirectories in the directory where merecat will run. With the example above, you'd do like so:
  mkdir www.acme.com www.joe.acme.com www.jane.acme.com
If you're using old-style multiple-IP multihosting, you should also create symbolic links from the numeric addresses to the names, like so:
  ln -s www.acme.com 192.100.66.1 
  ln -s www.joe.acme.com 192.100.66.200 
  ln -s www.jane.acme.com 192.100.66.201
This lets the older HTTP/1.0 browsers find the right subdirectory.
There is an optional alternate step three if you're using multiple-IP multihosting: run a separate merecat process for each hostname This gives you more flexibility, since you can run each of these processes in separate directories, with different throttle files, etc. Example:
  merecat -r         /usr/www      www.acme.com 
  merecat -r -u joe  /usr/www/joe  www.joe.acme.com 
  merecat -r -u jane /usr/www/jane www.jane.acme.com
Remember, this multiple-process method does not work with CNAME multihosting — for that, you must use a single merecat process with the -v flag.

merecat lets you define your own custom error pages for the various HTTP errors. There is a separate file for each error number, all stored in one special directory. The directory name is errors/, at the top of the web directory tree. The error files should be named errNNN.html, where NNN is the error number. So for example, to make a custom error page for the authentication failure error, which is number 401, you would put your HTML into the file errors/err401.html. If no custom error file is found for a given error number, then the usual built-in error page is generated.
In a virtual hosts setup you can also have different custom error pages for each host. In this case you put another errors/ directory in the top of that virtual host's web tree. merecat will look first in the virtual host errors directory, and then in the server-wide errors directory, and if neither of those has an appropriate error file then it will generate the built-in error.

Sometimes another site on the net will embed your image files in their HTML files, which basically means they're stealing your bandwidth. You can prevent them from doing this by using non-local referer filtering. With this option, certain files can only be fetched via a local referer. The files have to be referenced by a local web page. If a web page on some other site references the files, that fetch will be blocked. There are three config file variables for this feature:
**.jpg|**.gif|**.au|**.wav
A wildcard pattern for the URLs that should require a local referer. This is typically just image files, sound files, and so on. For example:
  urlpat = "**.jpg|**.gif|**.au|**.wav"
    
For most sites, that one setting is all you need to enable referer filtering.
<true | false>
By default, requests with no referer at all, or a null referer, or a referer with no apparent hostname, are allowed. With this variable set, such requests are disallowed.
PATTERN
A wildcard pattern that specifies the local host or hosts. This is used to determine if the host in the referer is local or not. If not specified it defaults to the actual local hostname.

merecat is very picky about symbolic links. Before delivering any file, it first checks each element in the path to see if it is a symbolic link, and expands them all out to get the final actual filename.
Along the way it checks for things like links with “..” that go above the server's directory, and absolute symlinks (ones that start with a /). These are prohibited as security holes, so the server returns an error page for them.
This means you cannot set up your web directory with a bunch of symlinks pointing to individual users' home web directories. Instead you do it the other way around — the user web directories are real subdirectories of the main web directory, and in each user's home directory there is a symlink pointing to their actual web directory.
The CGI pattern is also affected — it gets matched against the fully-expanded filename. So, if you have a single CGI directory but then put a symbolic link in it pointing somewhere else, that will not work. The CGI program will be treated as a regular file and returned to the client, instead of getting run. This could be confusing.

merecat is also picky about file permissions. It wants data files (HTML, images) to be world readable. Readable by the group that the merecat process runs as is not enough — merecat checks explicitly for the world-readable bit. This is so that no one ever gets surprised by a file that's not set world-readable and yet somehow is readable by the HTTP server and therefore the *whole* world.
The same logic applies to directories. As with the standard UNIX ls program, merecat will only let you look at the contents of a directory if its read bit is on; but as with data files, this must be the world-read bit, not just the group-read bit.
merecat also wants the execute bit to be *off* for data files. A file that is marked executable but doesn't match the CGI pattern might be a script or program that got accidentally left in the wrong directory. Allowing people to fetch the contents of the file might be a security breach, so this is prohibited. Of course if an executable file *does* match the CGI pattern, then it just gets run as a CGI.
In summary, data files should be mode 644 (rw-r--r--), directories should be 755 (rwxr-xr-x) if you want to allow indexing and 711 (rwx--x--x) to disallow it, and CGI programs should be mode 755 (rwxr-xr-x) or 711 (rwx--x--x).

merecat does all of its logging via syslog(3). All log messages are prepended with the program name, unless the command line option -I IDENT is used. The facility defaults to LOG_DAEMON. Aside from error messages, there are only a few log entry types of interest, all fairly similar to CERN Common Log Format:
  Aug  6 15:40:34 acme merecat[583]: 165.113.207.103 - - "GET /file" 200 357 
  Aug  6 15:40:43 acme merecat[583]: 165.113.207.103 - - "HEAD /file" 200 0 
  Aug  6 15:41:16 acme merecat[583]: referer http://www.acme.com/ -> /dir 
  Aug  6 15:41:16 acme merecat[583]: user-agent Mozilla/1.1N
Note that merecat does not translate numeric IP addresses into domain names. This is both to save time and as a minor security measure (the numeric address is harder to spoof).
If started in the foreground, -n, and with debug log level, -l debug, logs will also be printed on stderr, unless the user also requested -s. However, not all systems support the LOG_PERROR option to openlog().
Relevant merecat.h define: LOG_FACILITY.

merecat handles a couple of signals, which you can send via the standard UNIX kill(1) command:
These signals tell merecat to shut down immediately.
This signal tells merecat to toggle log level, between current log level and LOG_DEBUG. If merecat was started with LOG_DEBUG the toggle will be to LOG_NOTICE, which is the default log level.
This signal tells merecat to generate the statistics syslog messages immediately, instead of waiting for the regular hourly update.

merecat.conf(5), ssi(8), htpasswd(1)


Jef Poskanzer ⟨jef@mail.acme.com⟩ wrote the famous thttpd which merecat is based on.
Joachim Wiberg ⟨troglobit@gmail.com⟩ introduced all new shiny bugs.

merecat is a fork of sthttpd, which in turn is a fork of thttpd. So first and foremost, a huge thanks to Jef Poskanzer for creating thttpd and making it open source under the simplified 2-clause BSD license! Anthony G. Basile deserves another thank you, for merging Gentoo patches and refactoring the build system in sthttpd.
Also, many thanks to contributors, reviewers, testers: John LoVerso, Jordan Hayes, Chris Torek, Jim Thompson, Barton Schaffer, Geoff Adams, Dan Kegel, John Hascall, Bennett Todd, KIKUCHI Takahiro, Catalin Ionescu, Anders Bornäs, and Martin Olsson. Special thanks to Craig Leres for substantial debugging and development during the early days of thttpd.
August 3, 2019 merecat (2.32)
merecat-2.31+git20220513+ds/www/merecat.conf.5.html000066400000000000000000000461561424066547300213270ustar00rootroot00000000000000 merecat.conf.5
MERECAT.CONF(5) File Formats Manual MERECAT.CONF(5)

merecat.conf
merecat httpd configuration file

When merecat starts up it looks for its configuration file, /etc/merecat.conf. This manual page documents the settings available, which allows for more advanced setups. For simpler use-cases, however, you may not need a merecat.conf since the server runs fine with only command line parameters.

The syntax of the config file is UNIX style key = value, separated by whitespace. The “#” character marks the start of a comment to end of line. The \ character can be used as an escape character.
Note: changes to the configuration file are require a restart of merecat, unlike many other UNIX daemons SIGHUP does not reload the .conf file.

UTF-8
Character set to use with text MIME types. If the default unicode charset causes trouble, try "iso-8859-1".
<true | false>
Enable check for external sites referencing material on your web server. For more information on referrers, see merecat(8). Disabled by default.
<true | false>
For increased security, set this to true. Unless running chrooted in which case this is not really necessary. Disabled by default.
<true | false>
Change web server root to WEBDIR, or the current directory, if no WEBDIR is given as argument. Chrooting is a security measure and means that merecat.conf cannot access files outside it, unless files are bind mounted, or similar into the chroot. Disabled by default.
-1..9
Control the compression level of the built-in Apache-like mod_deflate. The default value is -1, which gives a reasonable compromize between speed and compression. To disable compression set this to 0 and to get maximum compression, 9.
The default setting, -1, means all "text/*" MIME type files, larger than 256 bytes, are compressed before sending to the client.
DIR
If no WEBDIR is given on the command line this option can be used to change the web server document root. Defaults to the current directory.
DIR
When chrooting this can be used to adjust the web server document root.
<true | false>
Set this to true to protect the entire directory tree with a single .htpasswd and/or .htaccess file. When unset, which is the default, merecat.conf looks for a local .htpasswd and .htaccess file, or serves the file without password.
HOSTNAME
The hostname to bind to when multihoming. For more details on this, see below discussion.
<true | false>
If dotfiles should be skipped in directory listings. Disabled by default.
PATTERN
Used with check-referer, see merecat(8) for more details.
SEC
Max number of seconds to be used in a “Cache-Control: max-age” header to be returned with all responses. An equivalent “Expires” header is also generated. The default is no Cache-Control or Expires headers, which is just fine for most sites.
PORT
The web server Internet port to listen to, defaults to 80, or 443 when HTTPS is enabled. See the ssl section below for more on configuring an HTTPS server.
PATTERN
Used with check-referer, see merecat(8) for more details.
NAME
Set username to drop privileges to after startup. Defaults to "nobody" which usually is defined on all UNIX systems.
<true | false>
Enable virtual hosting, disabled by default. For more information on this, see merecat(8).
PATTERN
Wildcard pattern to deny access to illicit hammering bots. When set a matching user-agent will receive a 403 for all its requests. Use for instance “**SemrushBot**” or “**SemrushBot**|**MJ12Bot**|**DotBot**” to match multiple user-agents. The default is to allow all user-agents.
PATTERN{
Wildcard pattern for CGI programs, for instance “**.cgi” or “**.cgi|/cgi-bin/*”. See the dedicated CGI section in merecat(8) for more on this.
<true | false>
The CGI module is disabled by default.
NUM
Maximum number of allowed simultaneous CGI programs. Default 1.
 
PATTERN{
Wildcard pattern for PHP scripts, for instance “**.php” or “**.php5|**.php4|**.php”.
<true | false>
The PHP module is disabled by default.
/path/to/php-cgi
Default is “/usr/bin/php-cgi
 
PATTERN{
Wildcard pattern for triggering SSI, for instance “**.shtml” or “**.shtml|**.stm|**.shtm”.
<true | false>
The SSI module is disabled by default.
/path/to/ssi
Default is “cgi-bin/ssi”. See ssi(8) for more information.
<true | false>
This setting can be used to silence “[an error occurred while processing the directive]”, shown when an error occurrs during SSI processing. Default disabled (false).
 
{
PROTOCOL
Minimum SSL/TLS protocol level to enable. Can be one of: SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3. The default minimum protocol is TLSv1.1. Note, some (Linux) distributions have SSLv3 disabled by default in their OpenSSL packages.
CIPHERS
The preferred list of ciphers the server supports. For a list of available ciphers, see the ciphers(1) man page. The default covers both TLSv1.3 (new ciphersuite) and older cipher list:
TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256: \ 
HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4:!DHE-RSA-CAMELLIA256-SHA:             \ 
!DHE-RSA-CAMELLIA128-SHA:!ECDHE-RSA-CHACHA20-POLY1305:                      \ 
!DHE-RSA-CHACHA20-POLY1305:!DHE-RSA-AES256-CCM8:!DHE-RSA-AES256-CCM:        \ 
!DHE-RSA-AES128-CCM8:!DHE-RSA-AES128-CCM
        
/path/to/cert.pem
Public part of HTTPS certificate, required for HTTPS.
/path/to/key.pem
Private key of HTTPS certificate, required for HTTPS. This file must be kept private and should not be in the WEBROOT directory.
/path/th/dhparam.pem
Optional Diffie-Hellman parameters. Not secret, unlike the keyfile the dhfile can be published online, if necessary. Create one like this:
openssl dhparam -out dhparam.pem 2048
        
 
name {
PORT
Server port to listen to.
{ ... }
Same as the global settings, above, only this is for this server only.
PATTERN” {
CODE
HTTP redirect code to use, default: 301. Supported codes are: 301, 302, 303, 307.
proto://$host:port$request_uri$args
Location to return for redirect, e.g. to redirect all request for HTTP to HTTPS for the same (virtual) host:
redirect "/**" { 
    code = 301 
    location = "https://$host$request_uri$args" 
}
            
 
 


Jef Poskanzer ⟨jef@mail.acme.com⟩ wrote the famous thttpd which merecat.conf is based on.
Joachim Wiberg ⟨troglobit@gmail.com⟩ added the .conf file parser and this man page.
August 3, 2019 merecat (2.32)
merecat-2.31+git20220513+ds/www/phpinfo.php000066400000000000000000000000251424066547300200670ustar00rootroot00000000000000 merecat-2.31+git20220513+ds/www/ssi.8.html000066400000000000000000000232471424066547300175600ustar00rootroot00000000000000 ssi.8
SSI(8) System Manager's Manual (smm) SSI(8)

ssi
Server-Side-Includes CGI program

ssi

This is an external CGI program that provides the same functionality as the built-in server-side-includes feature in many HTTP daemons. It is written for use with thttpd(8) and merecat(8), but should be easy to adapt to other systems.
There are two ways to use this; the modern way of using a .shtml pattern in merecat.conf(5) to trigger the SSI script, which requires enabling the SSI module. Then there is the traditional thttpd(8) approach. We start with the relevant settings needed in merecat.conf:
ssi { 
    enabled = true 
    pattern = "**.shtml"	# default 
    cgi-path = "cgi-bin/ssi"    # default, web server root is used 
}
The traditional thttpd way runs ssi as a simple CGI script, which requires placing the ssi binary in the web server CGI area, and enabling CGI. Then set up URLs with the path to the document to parse as the "pathinfo". That's the part of the URL that comes after the CGI program name. For example, if the URL to this program is:

    http://www.acme.com/cgi-bin/ssi 

and the url for the document is:

    http://www.acme.com/users/wecoyote/doc.html 

then the compound URL would be:

    http://www.acme.com/cgi-bin/ssi/users/wecoyote/doc.html 

The format description below is adapted from ⟨http://hoohoo.ncsa.uiuc.edu/docs/tutorials/includes.html⟩.
All directives are formatted as SGML comments within the document. This is in case the document should ever find itself in the client's hands unparsed. Each directive has the following format:

    <!--#command tag1="value1" tag2="value2" --> 

Note: the lack of space between the initial HTML comment start and the #command. This is explicitly stated in the standard and strictly enforced by all web servers implementing SSI.
Each command takes different arguments, most only accept one tag at a time. Here is a breakdown of the commands and their associated tags:
The config directive controls various aspects of the file parsing. There are two valid tags:
gives the server a new format to use when providing dates. This is a string compatible with the strftime(3) library call.
determines the formatting to be used when displaying the size of a file. Valid choices are bytes, for a formatted byte count (formatted as 1,234,567), or abbrev for an abbreviated version displaying the number of kilobytes or megabytes the file occupies.
overrides the default; “[an error occurred while processing this directive]”
Inserts the text of another document into the parsed document. The inserted file is parsed recursively, so it can contain server-side-include directives too. This command accepts two tags:
Gives a virtual path to a document on the server.
Gives a pathname relative to the current directory. ../ cannot be used in this pathname, nor can absolute paths be used.
Prints the value of one of the include variables (defined below). Any dates are printed subject to the currently configured timefmt. The only valid tag to this command is var, whose value is the name of the variable you wish to echo.
prints the size of the specified file, subject to the sizefmt parameter to the config command. Valid tags are the same as with the include command.
prints the last modification date of the specified file, subject to the formatting preference given by the timefmt parameter to config. Valid tags are the same as with the include command.

A number of variables are made available to parsed documents. In addition to the CGI variable set, the following variables are made available:
The current filename.
The virtual path to this document (such as /~robm/foo.shtml).
The unescaped version of any search query the client sent.
The current date, local time zone. Subject to the timefmt parameter to the config command.
Same as DATE_LOCAL but in Greenwich mean time (GMT).
The last modification date of the current document. Subject to timefmt like the others.

merecat(8), merecat.conf(5), strftime(3)


Jef Poskanzer ⟨jef@mail.acme.com⟩ wrote the original for use with thttpd.
Joachim Wiberg ⟨troglobit@gmail.com⟩ added minor features and a trigger in merecat for .shtml pages.

Does not implement all "modern" SSI directives are supported. E.g., exec cgi and exec cmd or any control directives like if, elif, else, endif, etc. Patches and pull-requests are welcome :)
August 3, 2019 merecat (2.32)
merecat-2.31+git20220513+ds/www/test.php000066400000000000000000000000361424066547300174050ustar00rootroot00000000000000